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;
}