1. 程式人生 > 其它 >Codeforces Round #749 A~E題解

Codeforces Round #749 A~E題解

傳送門


這場比賽打的還真不錯!1h20min切了四道題,E題想了一半,題解的方法確實簡單。

現在想想感覺還是比較快速的進入了cf的思維模式,通過構造一些特殊情況找到了突破口,順著往下想後實現的程式碼也不難。

A

這題放在cf的A題也挺合適。首先如果整個序列的和是合數,顯然輸出這\(n\)個數。否則刪去一個偶數,輸出\(n-1\)個數。因為題中滿足\(n \leqslant 3\),那麼如果和為奇數的話,必然存在一個偶數,刪去後和就是偶數。

int n, a[105];
In bool check(int x)
{
	for(int i = 2; i < x; ++i)
		if(x % i == 0) return 1;
	return 0;
}

int main()
{
//	MYFILE();
	int T = read();
	while(T--)
	{
		n = read();
		int sum = 0;
		for(int i = 1; i <= n; ++i) a[i] = read(), sum += a[i];
		if(check(sum))
		{
			write(n), enter;
			for(int i = 1; i <= n; ++i) write(i), space; enter;
		}
		else
		{
			write(n - 1), enter;
			int tp = 1;
			for(int i = 1; i <= n; ++i)
			{
				bool flg = a[i] & 1;
				if(tp && flg)
				{
					tp = 0;
					continue;
				}
				else write(i), space;
			}
			enter;
		}
	}
	return 0;
}

B

題意:讓你構造一棵樹, 滿足題目中的\(m\)條限制:節點\(a_i,c_i\)之間的路徑上不能有\(b_i\)\(1 < m < n \leqslant 3 \times 10 ^ 5\)

看起來挺嚇人,但注意到\(m < n\)原題還加粗了哈哈),那麼一定存在一個點沒有出現在任何\(b_i\)之中,因此那這個點作為菊花的中心,構造菊花即可。

int vis[maxn];
int main()
{
	int T = read();
	while(T--)
	{
		int n = read(), m = read();
		fill(vis + 1, vis + n + 1, 0);
		for(int i = 1; i <= m; ++i)
		{
			int a = read(), b = read(), c = read();
			vis[b] = 1;
		}
		int pos = 0;
		for(int i = 1; i <= n; ++i)
			if(!vis[i]) {pos = i; break;}
		for(int i = 1; i <= n; ++i)
			if(i != pos) write(i), space, write(pos), enter;
	}
	return 0;
}

C

這題題面挺長的,懶得翻譯了……
我覺得這題還行,不知道為啥隊友卡住了。

首先能觀察到的一點是,如果一個格子的左邊和上面都是'X'(記為情況1),那這個點一定是'N',但是從"NE"圖來看,無法確定這個點是'X'還是'.',此時包含這個點的子矩陣就不能確定。

進一步想,如果一個點在"NE"圖上是'N',那麼一定是他的左邊和上面都是'N',而左邊和上面是'N',一定是因為他們的左邊和上面都是'N',如此遞迴下去,第一個是'N'的點一定是上面的情況1。也就是說,任意一個點為'N',必然是因為出現了情況1。那麼判斷一個子矩陣能否確定,只用檢測情況1是否出現即可。

這個用一個二維字首和維護即可,但因為題目中子矩陣行數固定,可以簡化為一維的字首和。

char s[maxn];
int n, m, Q;
vector<vector<char> > v;
int sum[maxn], a[maxn];
int main()
{
	n = read(), m = read();
	vector<char> tp(m + 1);
	v.push_back(tp);
	for(int i = 1; i <= n; ++i)
	{
		scanf("%s", s + 1);
		v.push_back(tp);
		for(int j = 1; j <= m; ++j) v[i][j] = s[j];
	}
	for(int i = 1; i < m; ++i)
	{
		for(int j = 1; j < n; ++j)
			if(v[j + 1][i] == 'X' && v[j][i + 1] == 'X') a[i] = 1;
		sum[i] = sum[i - 1] + a[i];
	}
	Q = read();
	for(int i = 1; i <= Q; ++i)
	{
		int L = read(), R = read();
		if(L == R) puts("YES");
		else puts(sum[R - 1] - sum[L - 1] ? "NO" : "YES");
	}
	return 0;
}

D

D題整了一個小互動,還挺有意思。

題意:讓你猜一個長度為\(n\)的排列\(\{p_i\}\),每一次猜的格式是這樣的:你輸入一個長度為\(n\)的序列\(\{a_i\}\),但\(a_i\)不能是長度為\(n\)的一個排列,互動系統會計算\(s_i=a_i+p_i\),並返回所有出現次數超過1次的\(s_k\)的下標\(k\),如果有多個,返回最小下標;如果沒有,返回0。你一共可以問不超過\(2n\)次。

我當時想,如果\(\{a_i\}\)全是\(1\),只能返回0,但是如果在任意一個位置\(i\)有一個\(2\),那麼如果返回一個非零數\(k\),必然有\(p_k+1=p_i+2\),即\(p_i-p_k=1\),這樣我們就找到了一對大小關係。

但是僅僅列舉所有\(2\)的位置,並不能得到想要的所有\(n-1\)對大小關係,比如\(\{p_i\} = \{1,3,2\},\{a_i\} = \{2, 1, 1\}\),那麼會返回\(k=1\),這樣只能得到\(p_1-p_1=1\)這樣的矛盾式,我們想要的是返回\(k=3\),但實際上返回了\(k=1\)。因此,可以再構造有\(n-1\)\(2\),一個\(1\)這樣的“對偶序列”,就可以解決剛剛的問題了。(即構造\(\{a_i\} = \{2, 2, 1\}\),那麼會返回\(k=1\),但此時有\(p_3-p_1=1\)

這樣構造\(2n\)個序列後,必然會得到\(n-1\)對合法的大小關係,最後連邊找鏈的頭即可。

int n;

int du[maxn], nxt[maxn], ans[maxn];
In void addEdge(int x, int y)
{
	du[y]++;
	nxt[x] = y;
}

int main()
{
	n = read();
	for(int i = 1; i <= n; ++i)
	{
		putchar('?'), space;
		for(int j = 1; j <= n; ++j) write(i == j ? 2 : 1), space; enter;
		fflush(stdout);
		int x = read();
		if(x != i && x != 0) addEdge(i, x);
		putchar('?'), space;
		for(int j = 1; j <= n; ++j) write(i == j ? 1 : 2), space; enter;
		fflush(stdout);
		x = read();		
		if(x != i && x != 0) addEdge(x, i);
	}
	int sta = 0;
	for(int i = 1; i <= n; ++i) if(!du[i]) {sta = i; break;}
	for(int i = 1; i <= n; ++i)
	{
		ans[sta] = i;
		sta = nxt[sta];
	}
	putchar('!'), space;
	for(int i = 1; i <= n; ++i) write(ans[i]), space; enter;
	fflush(stdout);
	return 0;
}

E

E題想出來一半,另一半還真沒往那個方向想。

題意:給一張\(n\)個點,\(m\)條邊的無向圖,每一條邊的初始權值都是\(0\)。再給你\(q\)個操作,每個詢問形如\((a_i,b_i)\)。讓你規劃找一條從\(a_i\)\(b_i\)的簡單路徑,把路徑上每條邊的權值加\(1\),問能否在\(q\)個操作後,使所有邊的邊權都是偶數。
能的話,輸出方案;否則輸出輸出最少要新增幾次操作滿足所有邊權為偶數的條件。

首先把所有操作看作無向邊,構建一個新的圖。那麼如果能滿足題目“所有邊的邊權為偶數”的條件,必然能把這個新圖拆成若干個簡單環。

因為在一個長度為\(S\)的簡單環內的操作,我們可以讓前\(S-1\)條邊對應的操作在原圖上走簡單路徑,第\(S\)個操作直接沿前\(S-1\)個操作的路徑反過來走一遍即可!而環和環之間是互不影響的。

有了這個結論,首先能判斷無解的條件:即這個結論的逆否命題,如果新圖不能拆成若干個簡單環,必然不滿足題目的條件。而如何判斷,這個應該比較經典了,只要存在度數為奇數的點,那麼這張圖一定不能拆成若干個簡單環。記度數為奇數的點有\(x\)個,那麼還要新增\(\frac{x}{2}\)條邊使其構成簡單環(\(x\)一定是偶數)。

而對於有解的情況,我就卡在這了。因為雖然對於前\(S-1\)條邊對應的操作,可以在原圖上\(O(n)\)找出一條簡單路徑,但是不能保證他們合起來的路徑是簡單路徑,即最後一個操作不能滿足是簡單路徑。

題解中給出了很完美的解答:沿著最小生成樹上的路徑跑。

別說,還真是。對於一個簡單環內的所有操作,沿著最小生成樹上的邊跑,首先滿足了兩點之間的路徑一定是簡單路徑,其次一定保證了每條邊只經過了偶數次。關於第二點,我只是想出了一個略微感性的證明:將這個環的起點\(s\)作為最小生成樹的根節點,那麼這些路徑一定是從\(s\)出發,最終回到根節點\(s\)。因此對於\(s\)的所有出邊,如果會向下遞迴一次,使邊權加1,也一定會回溯,使邊權再加1,那麼這條邊的邊權還是偶數。這樣遞迴到每個子樹中,也一定如果向子樹內遞迴,必然會回溯到子樹的根節點。綜上所述,每條邊一定經過了偶數次。(也不知道我這個證明對不對……)

#include<bits/stdc++.h>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 3e5 + 5;
In ll read()
{
	ll ans = 0;
	char ch = getchar(), las = ' ';
	while(!isdigit(ch)) las = ch, ch = getchar();
	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
	if(las == '-') ans = -ans;
	return ans;
}
In void write(ll x)
{
	if(x < 0) x = -x, putchar('-');
	if(x >= 10) write(x / 10);
	putchar(x % 10 + '0');
}

int n, m, Q, du[maxn];
struct Edge
{
	int nxt, to;
}e[maxn << 1];
int head[maxn], ecnt = -1;
In void addEdge(int x, int y)
{
	e[++ecnt] = (Edge){head[x], y};
	head[x] = ecnt;
}

#define pr pair<int, int>
#define mp make_pair
#define F first
#define S second 
pr edges[maxn], q[maxn];

In void solve()
{
	int sum = 0;
	for(int i = 1; i <= n; ++i)
		if(du[i] & 1) sum++;
	puts("NO");
	write(sum / 2), enter;
}

int p[maxn];
In int Find(int x) {return x == p[x] ? x : p[x] = Find(p[x]);}

int ans[maxn];
In bool dfs(int now, int _f, int p, int tot)
{
	if(now == p)
	{
		ans[0] = tot;
		return 1;	
	}
	forE(i, now, v)
	{
		if(v == _f) continue;
		ans[tot + 1] = v;
		if(dfs(v, now, p, tot + 1)) return 1;
	}
	return 0;
}

int main()
{
	Mem(head, -1), ecnt = -1; 
	n = read(), m = read();
	for(int i = 1; i <= m; ++i)
	{
		int x = read(), y = read();
		edges[i] = mp(x, y);
	}
	Q = read();
	for(int i = 1; i <= Q; ++i)
	{
		int x = read(), y = read();
		du[x]++, du[y]++;
		q[i] = mp(x, y);
	}
	for(int i = 1; i < n; ++i) if(du[i] & 1) {solve(); return 0;}
	int cnt = 0;
	for(int i = 1; i <= n; ++i) p[i] = i; 
	for(int i = 1; i <= m; ++i)
	{
		int x = edges[i].F, y = edges[i].S;
		int px = Find(x), py = Find(y);
		if(px != py)
		{
			p[px] = py;
			addEdge(x, y), addEdge(y, x);
			if(++cnt == n - 1) break;
		}
	}
	puts("YES");
	for(int i = 1; i <= Q; ++i)
	{
		ans[1] = q[i].F;
		dfs(q[i].F, 0, q[i].S, 1);
		write(ans[0]), enter;
		for(int j = 1; j <= ans[0]; ++j) write(ans[j]), space; enter;
	}
	return 0;
}