Codeforces LATOKEN Round 1 (Div. 1 + Div. 2) A~E題解
本場連結:Codeforces LATOKEN Round 1 (Div. 1 + Div. 2)
A. Colour the Flag
首先不難想到一個直接的做法:從每個已經有的元素開始跑BFS
直接把所有格子拓展出來,如果發現矛盾即可退出,順便把答案構造出來。但是不難想到:整個局面之和左上角的元素有關,也就是說合法的局面一共只有兩種,所以不妨把兩種結果直接構造出來再檢查是否是兩種合法的局面中的一種,即可確定答案。
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define forn(i,x,n) for(int i = x;i <= n;++i) #define forr(i,x,n) for(int i = n;i >= x;--i) #define Angel_Dust ios::sync_with_stdio(0);cin.tie(0) const int N = 55; char s[N][N],ans[N][N]; int main() { int T;scanf("%d",&T); while(T--) { int n,m;scanf("%d%d",&n,&m); forn(i,1,n) scanf("%s",s[i] + 1); forn(i,1,n) forn(j,1,m) if((i + j) % 2) ans[i][j] = 'R';else ans[i][j] = 'W'; bool ok = 1; forn(i,1,n) forn(j,1,m) if(ans[i][j] != s[i][j] && s[i][j] != '.') ok = 0; if(ok) { puts("YES"); forn(i,1,n) { forn(j,1,m) printf("%c",ans[i][j]); puts(""); } continue; } ok = 1; forn(i,1,n) forn(j,1,m) if((i + j) % 2) ans[i][j] = 'W';else ans[i][j] = 'R'; forn(i,1,n) forn(j,1,m) if(ans[i][j] != s[i][j] && s[i][j] != '.') ok = 0; if(ok) { puts("YES"); forn(i,1,n) { forn(j,1,m) printf("%c",ans[i][j]); puts(""); } continue; } puts("NO"); } return 0; }
B. Histogram Ugliness
只有當削掉某個比身邊兩個元素都高的元素的時候,通過\(1\)的代價得到了\(2\)的收益,所以只有當刪掉比兩邊都高的元素的時候才會獲得正的收益。如此即可構造答案。
#include <bits/stdc++.h> using namespace std; typedef long long ll; #define forn(i,x,n) for(int i = x;i <= n;++i) #define forr(i,x,n) for(int i = n;i >= x;--i) #define Angel_Dust ios::sync_with_stdio(0);cin.tie(0) const int N = 4e5+7; ll a[N],s[N]; int main() { int T;scanf("%d",&T); while(T--) { int n;scanf("%d",&n); forn(i,1,n) scanf("%d",&a[i]);a[n + 1] = 0; ll res = 0; forn(i,1,n) { ll nxt = min(a[i],max(a[i - 1],a[i + 1])); res += abs(a[i] - nxt); a[i] = nxt; } forn(i,1,n + 1) res += abs(a[i] - a[i - 1]); printf("%lld\n",res); } return 0; }
C. Little Alawn's Puzzle
構造一個有向圖:從\(a[i]\)向\(b[i]\)連一條有向邊,這樣構成的圖上,一個環上所有元素一同交換可以保證正確性。所以答案=\(2^k\)其中\(k\)是圖上的環的數量,dfs
求連通分量的個數即可。
#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pii; #define forn(i,x,n) for(int i = x;i <= n;++i) #define forr(i,x,n) for(int i = n;i >= x;--i) #define Angel_Dust ios::sync_with_stdio(0);cin.tie(0) #define x first #define y second const int N = 4e5+7,M = 2 * N,MOD = 1e9+7; int a[N],b[N]; int edge[M],succ[M],ver[N],idx; bool st[N]; void add(int u,int v) { edge[idx] = v; succ[idx] = ver[u]; ver[u] = idx++; } void dfs(int u,int fa) { st[u] = 1; for(int i = ver[u];~i;i = succ[i]) { int v = edge[i]; if(v == fa || st[v]) continue; dfs(v,u); } } int qpow(int a,int b,int MOD) { int res = 1; while(b) { if(b & 1) res = 1ll * a * res % MOD; a = 1ll * a * a % MOD; b >>= 1; } return res; } int main() { int T;scanf("%d",&T); while(T--) { int n;scanf("%d",&n); forn(i,1,n) ver[i] = -1;idx = 0; forn(i,1,n) st[i] = 0; forn(i,1,n) scanf("%d",&a[i]); forn(i,1,n) scanf("%d",&b[i]); forn(i,1,n) add(a[i],b[i]); int k = 0; forn(i,1,n) if(!st[i]) dfs(i,-1),++k; printf("%d\n",qpow(2,k,MOD)); } return 0; }
D. Lost Tree
不難想到一個直接的做法:把所有點都詢問一遍,所有與之距離為\(1\)的元素可以確定直接連邊,但是這樣做顯然詢問次數是\(n\)次。
考慮將詢問次數降低:因為和\(2\)有關,對於任意一棵樹他同時肯定是一個二分圖,對於一個二分圖與本題有關的形式可以想到對於每一條邊連結的兩個點,至少應該有一個點拿來被詢問,這是一個最大獨立集的形式。如此劃分:首先任意提一個點作為整個樹的樹根,對樹根詢問一次,將所有元素按照到根的距離的奇偶性劃分集合。為了保證詢問次數較少:只取兩個集合中大小較小的進行詢問,把所有距離為\(1\)的點加入集合即可。
特別地,為了保證詢問次數,開始的詢問一次也可以記錄下來避免多迴圈了一次。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
#define x first
#define y second
const int N = 2005;
bool st[N];
int main()
{
Angel_Dust;
int n;cin >> n;
set<pii> res;
queue<int> q;
cout << "? " << 1 << endl;
vector<int> d(n),s[2];
for(auto& v : d) cin >> v;
forn(i,0,n - 1) if(d[i] % 2 == 0) s[0].push_back(i + 1);else s[1].push_back(i + 1);
forn(i,0,n - 1) if(d[i] == 1) res.insert({max(i + 1,1),min(i + 1,1)});
if(s[0].size() > s[1].size()) swap(s[0],s[1]);
for(auto& u : s[0])
{
if(u == 1) continue;
cout << "? " << u << endl;
for(auto& v : d) cin >> v;
forn(i,0,n - 1) if(d[i] == 1) res.insert({max(i + 1,u),min(i + 1,u)});
}
cout << "!" << endl;
for(auto& v : res) cout << v.x << " " << v.y << endl;
return 0;
}
E. Lost Array
如果這個題\(n\)很小,可以考慮一個\(O(n2^n)\)的狀壓dp
:狀態\(f[i][S]\)表示使用\(i\)次詢問,當前構造出來的集合是\(S\),是否可行。如此找到最小的\(i\)滿足\(f[i][2^n-1]=1\)即為答案。
如何加速狀壓dp
?這顯然不是一個很好的想法,因為狀壓的開銷是必須的,所以必然不是狀壓:我們可以不需要直到當前構造的集合的具體形態:考慮一個dp
:
- 狀態:\(f[i]\)表示當前有\(i\)個\(1\)的局面是否可行。
- 入口:\(f[0] = 1\)
- 轉移:如果把\(x\)個\(1\)掰成\(0\),那麼之後會讓\(f[i]\)狀態轉移到\(f[i + k - 2 * x]\),以
bfs
遞推即可求出最短到達\(f[n]=1\)狀態的路徑:只需在bfs
的過程中記錄前驅即可。 - 出口:\(f[n]\)
特別地,如果\(f[n]\)不可到達,則無解。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
const int N = 505;
int a[N],dist[N],pre[N],val[N];
int n,k,ans;
void bfs()
{
memset(dist,0x3f,sizeof dist);memset(pre,-1,sizeof pre);dist[0] = 0;
queue<int> q;q.push(0);
while(!q.empty())
{
int u = q.front();q.pop();
forn(x,0,min(k,u))
{
int nxt = u + k - 2 * x;
if(nxt < 0 || k - x > n - u || nxt > n) continue;
if(dist[nxt] > dist[u] + 1)
{
dist[nxt] = dist[u] + 1;
pre[nxt] = u;
val[nxt] = x;
q.push(nxt);
}
}
}
}
void dfs(int cur)
{
if(~pre[cur]) dfs(pre[cur]);
else return;
cout << "? ";
for(int i = 1,c0 = k - val[cur],c1 = val[cur];(c0 || c1) && i <= n;++i)
{
if(a[i] && c1) --c1,a[i] ^= 1,cout << i << " ";
else if(!a[i] && c0) --c0,a[i] ^= 1,cout << i << " ";
}
cout << endl;
int xi;cin >> xi;
ans ^= xi;
}
int main()
{
Angel_Dust;
cin >> n >> k;
bfs();
if(dist[n] == 0x3f3f3f3f) return cout << "-1" << endl,0;
dfs(n);
cout << "! " << ans << endl;
return 0;
}