1. 程式人生 > >Codeforces Round #473 (Div. 2) (A-E) (水題 + 並查集 + 構造 + 分解因數 + 貪心)

Codeforces Round #473 (Div. 2) (A-E) (水題 + 並查集 + 構造 + 分解因數 + 貪心)

A

題意

給出一個數 nn,兩人每次可以選擇一個數 aa,使得 n=nan = n - a。其中a1a \geq 1 且第一個人選的a應為偶數,第二個人選的應為奇數,誰不能選的時候為負。誰不能操作為負,問誰能贏。

思路

判斷 nn 的奇偶性即可,先手偶數不改變奇偶性,後手奇數改變奇偶性,當 n==0n==0 時遊戲結束,也就是說僅判斷 nn 的奇偶性即可。

程式碼

int main()
{
	int n;
	sd(n);
	if(n&1) puts("Ehab");
	else puts("Mahmoud");	
	return 0;
}

B

題意

給出 n

n 個字串,以及他們的花費,然後將這些字串分組,每個串可以用同組的字串替換,然後給出 mm 個目標串,問得到目標串的最小花費

思路

解決的核心在於如何記錄每個字串屬於哪個組,以及該組的最小花費。一開始讀錯題了寫了個並查集,但是這裡也還能用。有更簡單的維護辦法

程式碼

map<string, int> mp;

int fa[maxn], cost[maxn];

void init(){
	rep(i, 0, maxn){
		fa[i] = i;
	}
}

int found(int x){
	return x==fa[x]?x:(fa[x] = found(fa[x]));
} void merg(int x, int y){ // fa[y] = x; int fax = found(x); int fay = found(y); if(fax == fay) return ; else { cost[fax] = min(cost[fax], cost[fay]); fa[fay] = fax; } } int main() { ios::sync_with_stdio(false); int n, k, m; init(); cin>>n>>k>>m; rep(i, 1, n+1){ string str;
cin>>str; // debug(str); mp[str] = i; } rep(i, 1, n+1){ int x; cin>>x; cost[i] = x; } rep(i, 0, k){ int num, fi, tmp; cin>>num>>fi; rep(i, 1, num){ cin>>tmp; merg(fi, tmp); } } ll ans = 0; rep(i, 0, m){ string str; cin>>str; // debug(str); int fas = found(mp[str]); // debug(fas); ans += cost[fas]; } cout<<ans<<endl; return 0; }

C

題意

在樹上求一個點集,要求樹上的任意一條邊,都至少有一個端點,在這個集合內,求集合內元素的最小個數。現給出一種演算法,以1號節點為根,按奇偶分層,答案為奇偶層節點分別求和的較小值。現需要構造出 nn 個節點的兩棵樹,第一棵不滿足演算法,第二棵滿足演算法。

思路

滿足條件的樹好構造,只需要所有節點連向 11 號節點,那麼 11 就是答案。不滿足條件的樹需要的一個條件是,答案應該同時小於奇數層結點數和偶數層節點數。經手推構造發現,當 n5n \leq 5 時 ,不存在。當 n6n\geq6 時,只需要 2&ThickSpace;3&ThickSpace;42\; 3 \; 4 號節點連向 11 其餘節點連向 22 即可。

程式碼

int main()
{
	int n;
	sd(n);
	
	if(n <= 5) puts("-1");
	else {
		rep(i, 2, n+1){
			printf("%d %d\n", i<5?1:2, i);
		}
	}

	rep(i, 2, n+1)
		printf("1 %d\n", i);

	return 0;
}

D

題意

給出一個序列 aa,需要構造一個數列 bb,使得 bb 序列的字典序大於等於 aa 序列,且 bb 序列中任意兩個元素兩兩互質,問滿足條件的,字典序最小的 bb 序列是多少。

思路

這道題很開心的一點是,又學會了一種預處理質因數分解的寫法。第一種,可以對於每個質數,遍歷 maxnmaxn 中它的倍數,加到vector中。第二種,只需要記錄當前數的最小質因子,不斷轉移即可。 然後就貪心求每個數,判斷當前數是否可用,以及加入當前數後,更新可用狀態

程式碼

int fac[maxn], vis[maxn];
int a[maxn], b[maxn];

void init(){
	rep(i, 2, maxn){
		if(!fac[i]){
			for(int j = i; j<maxn; j+=i) {  // minn fac
				if(!fac[j]) fac[j] = i;
			}
		}
	}
}

bool judge(int x){
	while(x > 1){
		if(vis[fac[x]]) return false;
		x = x / fac[x];
	}
	return true;
}

void push_up(int x){
	while(x > 1){
		vis[fac[x]] = 1;
		x = x / fac[x];
	}
}

int main()
{
	init();
	int n;
	sd(n);
	rep(i, 0, n) sd(a[i]);
	rep(i, 0, n) {
		if(judge(a[i])) {
			b[i] = a[i];
			push_up(a[i]);
		}
		else {
			int now = a[i], nowp = i;;
			while(!judge(now)) now++;
			b[nowp++] = now;
			push_up(now);
			now = 2;
			while(nowp < n){
				while(!judge(now)) now++;
				b[nowp++] = now;
				push_up(now);
			}
			break;
		}
	}
	rep(i, 0, n) {
		printf("%d%c", b[i], i==n-1?'\n':' ');
	}
	return 0;
}

E

題意

給出 nn 個點,編號為 [0,n)[0, n) 任意兩點之間的線段權值為兩點的亦或值。

思路

每次貪心的把最小花費的邊全部連上,即二進位制只有一位不同的部分,相當於連續合併。

程式碼

int main()
{
	ll n, sum = 0, bas = 1;
	sld(n);
	while(n > 1){
		sum += (n>>1) * bas;
		n = (n+1)>>1;
		bas <<= 1;
	}
	pld(sum);
	return 0;
}