1. 程式人生 > >第九屆福建省大學生程式設計競賽部分題解

第九屆福建省大學生程式設計競賽部分題解

比賽就這樣過去了,A2題水了個銅獎第三回來= =感覺能A3拿個銀獎的

目錄

A:Uint47 calculator

D:Number theory  

E:Traffic jam 

F:IoU 

G:Chosen by god    

I:Mind control      


 

A:Uint47 calculator

模擬47位計算,當時我沒想起來快速乘,結果用java大數,報了一個我見都沒見過的re,後來好久以後,問了工作人員,然後才重判A了,其實真的就是個水題,注意乘法會爆longlong,用快速乘就可以了

#include<iostream>
#include<string>
#include<map>
using namespace std;
typedef long long ll;
const ll mod = 1LL << 47;
ll quick_mul(ll a, ll b) {
	ll ans = 0;
	while (b) {
		if (b & 1)ans = (ans + a) % mod;
		a = (a + a) % mod;
		b >>= 1;
	}
	return ans;
}
int main() {
	string a, b, c;
	ll t;
	map<string, ll> m;
	while (cin >> a >> b) {
		if (a == "def") {
			cin >> t;
			m[b] = t;
		}
		else {
			cin >> c;
			if (a == "add")m[b] = (m[b] + m[c]) % mod;
			else if (a == "sub")m[b] = (m[b] - m[c] + mod) % mod;
			else if (a == "mul")m[b] = quick_mul(m[b], m[c]);
			else if (a == "div")m[b] = m[b] / m[c];
			else m[b] %= m[c];
		}
		cout << b << " = " << m[b] << endl;
	}
	return 0;
}

D:Number theory  

這題其實如果模一個質數就變成水題了,這題其實是一個線段樹的點修改+區間查詢,M操作可以看成把i節點修改成 yi,N操作可以看作吧di節點修改成1,然後每次的乘積就是區間1-10的乘積,如果看出來是線段樹,其實只是個模板題

#include<iostream>
#include<cstring>
typedef long long ll;
const int N = 400005;
ll mul[N], M;
void build(int rt, int L, int R) {
	if (L == R) {
		mul[rt] = 1;
		return;
	}
	int mid = (L + R) >> 1;
	build(rt << 1, L, mid);
	build(rt << 1 | 1, mid + 1, R);
	mul[rt] = mul[rt << 1] * mul[rt << 1 | 1] % M;
}
void update(int rt, int L, int R, const int &pos, const int &val) {
	if (L == R) {
		mul[rt] = val;
		return;
	}
	int mid = (L + R) >> 1;
	if (pos <= mid)update(rt << 1, L, mid, pos, val);
	else update(rt << 1 | 1, mid + 1, R, pos, val);
	mul[rt] = mul[rt << 1] * mul[rt << 1 | 1] % M;
}
int main() {
	int T, Q, x;
	scanf("%d", &T);
	char s[2];
	while (T--) {
		scanf("%d%lld", &Q, &M);
		build(1, 1, Q);
		for (int i = 1; i <= Q; ++i) {
			scanf("%s%d", s, &x);
			if (s[0] == 'M')update(1, 1, Q, i, x);
			else update(1, 1, Q, x, 1);
			printf("%lld\n", mul[1]);
		}
	}
	return 0;
}

E:Traffic jam 

這題其實是一個最短路徑,注意鬆弛不再是if(dis[v]>dis[now]+edge[now][i].second)了,要看能不能走(x=dis[now]/a[now],能走的話x%2==0),能走才是dis[now],不能的話,dis[now]要改成(x+1)*a[now],其他的就是正常的最短路徑,要注意a[now]可以為0,這時候不能除,不知為什麼當時這題沒有A出來

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
const int N = 1001, inf = 0x3f3f3f3f;
vector<pair<int, int> > edge[N];
int dis[N], a[N], s, t;
void dijkstra() {
	priority_queue<pair<int, int> >q;
	dis[s] = 0;
	q.push(make_pair(0, s));
	while (!q.empty()) {
		int now = q.top().second;
		int cost = dis[now];
		if (a[now]) {
			int x = dis[now] / a[now];
			if (x & 1)cost = (x + 1)*a[now];
		}
		if (now == t)return;
		q.pop();
		for (int i = 0; i < edge[now].size(); ++i) {
			int v = edge[now][i].first;
			if (dis[v] > cost + edge[now][i].second) {
				dis[v] = cost + edge[now][i].second;
				q.push(make_pair(-dis[v], v));
			}
		}
	}
}
int main() {
	int T, n, m, x, y, z;
	scanf("%d", &T);
	while (T--) {
		for (int i = 0; i < N; ++i)edge[i].clear();
		memset(dis, inf, sizeof(dis));
		scanf("%d%d", &n, &m);
		for (int i = 1; i <= n; ++i)scanf("%d", &a[i]);
		for (int i = 0; i < m; ++i) {
			scanf("%d%d%d", &x, &y, &z);
			edge[x].push_back(make_pair(y, z));
			edge[y].push_back(make_pair(x, z));
		}
		scanf("%d%d", &s, &t);
		dijkstra();
		printf("%d\n", dis[t]);
	}
	return 0;
}

F:IoU 

這題其實是最簡單的一個題目,分別求出長和寬就可以了

#include<iostream>
typedef long long ll;
ll min(const ll &a, const ll &b) { return a <= b ? a : b; }
ll max(const ll &a, const ll &b) { return a >= b ? a : b; }
int main() {
	int x1, y1, w1, h1, x2, y2, w2, h2, T;
	scanf("%d", &T);
	while (T--) {
		scanf("%d%d%d%d%d%d%d%d", &x1, &y1, &w1, &h1, &x2, &y2, &w2, &h2);
		ll overlap = 0, unionArea = w1 * h1 + w2 * h2;
		if (min(x1 + w1, x2 + w2) > max(x1, x2) && min(y1 + h1, y2 + h2) > max(y1, y2))
			overlap = (min(x1 + w1, x2 + w2) - max(x1, x2))*(min(y1 + h1, y2 + h2) - max(y1, y2));
		unionArea -= overlap;
		if (!unionArea)printf("0.00\n");
		else printf("%.2lf\n", 1.0*overlap / unionArea);
	}
	return 0;
}

G:Chosen by god    

這個奧數飛彈很騷,我當時根本沒看出來,導致沒看懂題目,其實就說攻擊分裂成n份,每次攻擊都是隨機的,

那麼也就是說n<m時為0,否則答案是\frac{\sum_{i=m}^{n}\binom{n}{i}}{2^{n}}

這題用楊輝三角+字尾和+費馬小定理求逆元

#include<iostream>
typedef long long ll;
const int N = 1001, mod = 1e9 + 7;
ll c[N][N] = { 1 }, inv[N] = { 1 };
ll quickPow(ll a, ll b) {
	ll ans = 1;
	while (b) {
		if (b & 1)ans = ans * a%mod;
		a = a * a%mod;
		b >>= 1;
	}
	return ans;
}
int main() {
	int T, n, m;
	ll t = 2;
	for (int i = 1; i < N; ++i) {
		inv[i] = quickPow(t, mod - 2);
		t = t * 2 % mod;
	}
	for (int i = 1; i < N; ++i)
		for (int j = 0; j <= i; ++j)c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
	for (int i = 1; i < N; ++i)
		for (int j = i - 1; j >= 0; --j)c[i][j] = (c[i][j] + c[i][j + 1]) % mod;
	scanf("%d", &T);
	while (T--) {
		scanf("%d%d", &n, &m);
		if (m == 0)printf("1\n");
		else printf("%lld\n", c[n][m] * inv[n] % mod);
	}
	return 0;
}

I:Mind control      

這題其實期望很好推\frac{\sum_{i=m}^{n}i\binom{i-1}{m-1}}{\binom{n}{m}},但是要化簡,當時不會化

\frac{\sum_{i=m}^{n}i\binom{i-1}{m-1}}{\binom{n}{m}} \\=\frac{\sum_{i=m}^{n}\frac{i*(i-1)!}{(m-1)!(m-i)!}}{\binom{n}{m}} \\=\frac{\sum_{i=m}^{n}\frac{m*i!}{m!(m-i)!}}{\binom{n}{m}} \\=\frac{\sum_{i=m}^{n}m\binom{i}{m}}{\binom{n}{m}} \\=\frac{m*\binom{n+1}{m+1}}{\binom{n}{m}} \\ =\frac{m*(n+1)}{m+1}

m+1就有逆元了,用逆元遞推的,不然會wa(不知道為什麼)

#include<iostream>
typedef long long ll;
const int mod = 1e9 + 7, N = 1e6 + 5;
ll inv[N] = { 0,1 };
int main(){
	for (int i = 2; i < N; i++)inv[i] = (mod - mod / i)*inv[mod%i] % mod;
	int T, n, m;
	scanf("%d", &T);
	while (T--){
		scanf("%d%d", &n, &m);
		printf("%lld\n", n <= m ? n : 1LL * m*(n + 1) % mod*inv[m + 1] % mod);
	}
	return 0;
}

剩下的題目會了再說吧