本场链接: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
",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
",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;
}