Codeforces Round 1046 (Div. 1)
Preface
抛开整个八月完全没写代码不谈,就 CF 来说感觉已经有一万年没打了
由于 NJU CS 预推免有机试,想着恢复下手感就现场打了这场,没想到 2h Div1 能出四题
看来摆烂确实有助于水平提高啊,这下不得不多摆烂会了
A. Against the Difference
直接 DP,令 \(f_i\) 表示前 \(i\) 个位置的答案,钦定 \(f_i\) 不降
转移的话就是找到最靠右的位置 \(j\),满足 \([j,i]\) 中当前的数 \(a_i\) 出现次数恰为 \(a_i\) 次,拿个 vector 随便维护即可
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,a[N],f[N],num[N]; vector <int> pos[N];
int main()
{
for (scanf("%d",&t);t;--t)
{
scanf("%d",&n);
for (RI i=1;i<=n;++i) pos[i].clear();
for (RI i=1;i<=n;++i)
{
scanf("%d",&a[i]);
pos[a[i]].push_back(i);
num[i]=(int)pos[a[i]].size();
}
for (RI i=1;i<=n;++i)
{
f[i]=f[i-1];
if (num[i]<a[i]) continue;
f[i]=max(f[i],f[pos[a[i]][num[i]-a[i]]-1]+a[i]);
}
printf("%d\n",f[n]);
}
return 0;
}
B. For the Champion
思博题
考虑先用 4 次操作,即 \(x,y\) 各加两次 \(10^9\),把当前位置移动到所有点的右上方,此时可以列出关于 \(X+Y\) 的一个方程
然后再用 4 次操作,即将 \(y\) 减去四次 \(10^9\),把当前位置移动到所有点的右下方,此时可以列出关于 \(X-Y\) 的一个方程
联立即可得到 \(X,Y\) 的值
#include<cstdio>
#include<iostream>
#include<vector>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=105,LEN=1e9;
int t,n;
signed main()
{
for (scanf("%lld",&t);t;--t)
{
scanf("%lld",&n);
int xpy_max=-1e18,ymx_min=1e18;
for (RI i=1;i<=n;++i)
{
int x,y; scanf("%lld%lld",&x,&y);
xpy_max=max(xpy_max,x+y);
ymx_min=min(ymx_min,y-x);
}
int tmp,A,B;
printf("? R %lld\n",LEN);
fflush(stdout); scanf("%lld",&tmp);
printf("? R %lld\n",LEN);
fflush(stdout); scanf("%lld",&tmp);
printf("? U %lld\n",LEN);
fflush(stdout); scanf("%lld",&tmp);
printf("? U %lld\n",LEN);
fflush(stdout); scanf("%lld",&A);
printf("? D %lld\n",LEN);
fflush(stdout); scanf("%lld",&tmp);
printf("? D %lld\n",LEN);
fflush(stdout); scanf("%lld",&tmp);
printf("? D %lld\n",LEN);
fflush(stdout); scanf("%lld",&tmp);
printf("? D %lld\n",LEN);
fflush(stdout); scanf("%lld",&B);
A=A+xpy_max-4LL*LEN;
B=B-ymx_min-4LL*LEN;
int X=(A+B)/2,Y=(A-B)/2;
printf("! %lld %lld\n",X,Y); fflush(stdout);
}
return 0;
}
C. By the Assignment
感觉很套路的一个题啊
简单手玩一下会发现题目的限制等价于:偶环上所有数都相同,奇环上所有数都必须为 \(0\)
因此可以考虑用并查集维护所有值一样的点以及它们对应的值
具体实现时,可以先求出原图的 DFS 生成树,此时所有非树边都是返祖边
加入一条非树边时,相当于将对应两点树上路径上的所有点连成一个集合,钦定并查集连边的方向后直接暴力的复杂度就是对的(因为最多合并 \(O(n)\) 次)
判掉无解的情况后,最后答案就是 \(V\) 的未确定的集合数量次方
当然这题也能用点双+染色来写,但感觉不如这种方法简单好写
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod=998244353;
int t,n,m,V,vis[N],dep[N],anc[N],fa[N],val[N],flag;
vector <int> v[N]; vector <pair <int,int>> E;
inline void DFS(CI now,CI lst=-1)
{
vis[now]=1;
for (auto to:v[now])
{
if (vis[to])
{
if (to!=lst) E.push_back({now,to});
continue;
}
anc[to]=now;
dep[to]=dep[now]+1;
DFS(to,now);
}
}
inline int getfa(CI x)
{
return x!=fa[x]?fa[x]=getfa(fa[x]):x;
}
inline int merge(CI a,CI b)
{
if (a==-1&&b==-1) return -1;
if (a==-1&&b!=-1) return b;
if (a!=-1&&b==-1) return a;
if (a==b) return a; else return flag=0,-1;
}
inline void link(int x,int y)
{
x=getfa(x); y=getfa(y);
fa[x]=y; val[y]=merge(val[x],val[y]);
}
int main()
{
for (scanf("%d",&t);t;--t)
{
scanf("%d%d%d",&n,&m,&V);
for (RI i=1;i<=n;++i)
{
scanf("%d",&val[i]);
v[i].clear(); vis[i]=0;
}
for (RI i=1;i<=m;++i)
{
int x,y; scanf("%d%d",&x,&y);
v[x].push_back(y); v[y].push_back(x);
}
E.clear(); dep[1]=1; DFS(1); flag=1;
for (RI i=1;i<=n;++i) fa[i]=i;
for (auto [x,y]:E)
{
int dlt=dep[y]-dep[x];
while (getfa(x)!=getfa(y))
{
x=getfa(x);
link(x,anc[x]);
}
if (dlt%2==0) val[getfa(y)]=merge(val[getfa(y)],0);
}
if (!flag) { puts("0"); continue; }
int ans=1;
for (RI i=1;i<=n;++i) vis[i]=0;
for (RI i=1;i<=n;++i)
{
int x=getfa(i);
if (vis[x]) continue;
vis[x]=1;
if (val[x]==-1) ans=1LL*ans*V%mod;
}
printf("%d\n",ans);
}
return 0;
}
D1. From the Unknown (Easy Version)
挺有意思的一个题,D1 感觉还是很好想的
考虑先问 \(n\) 个 \(1\),根据回答我们一定可以确定答案的一个可能范围 \(W\in [L,R]\)
通过打表不难发现 \(2L\ge R\) 始终成立,因此我们可以用如下方法进行第二次询问
不难发现一定存在一个分界点 \((L,M)\),使得它之前的相邻一对数分为一组,它之后的所有数单独分组
因此我们可以用 \(2\times (R-L)\) 的长度唯一确定 \(W\),最坏情况 \(L=50000,R=99999\),符合长度限制
#include<cstdio>
#include<iostream>
#include<vector>
#include<assert.h>
#define RI register int
#define CI const int&
using namespace std;
const int N=100000;
int t;
int main()
{
/*int mxd=0,ml,mr;
for (RI k=2;k<=100000;++k)
{
int L=(n+k-1)/k,R=n/(k-1);
while (R>=L&&(n+R-1)/R!=k) --R;
if (L>R) continue;
assert(2*L>=R);
if (R-L>mxd) mxd=R-L,ml=L,mr=R;
}
printf("%d %d\n",ml,mr);*/
for (scanf("%d",&t);t;--t)
{
printf("? %d ",N);
for (RI i=1;i<=N;++i)
printf("1%c"," \n"[i==N]);
fflush(stdout);
int k; scanf("%d",&k);
if (k==1)
{
printf("! %d\n",N); fflush(stdout);
continue;
}
int L=(N+k-1)/k,R=N/(k-1);
while (R>=L&&(N+R-1)/R!=k) --R;
if (L==R)
{
printf("! %d\n",L); fflush(stdout);
continue;
}
printf("? %d ",2*(R-L));
for (RI i=R-L;i>=1;--i)
printf("%d %d%c",L,i," \n"[i==1]);
fflush(stdout); scanf("%d",&k);
printf("! %d\n",R-(k-(R-L))); fflush(stdout);
}
return 0;
}
D2. From the Unknown (Hard Version)
其实比赛的时候 D2 的思路基本全出来了
但因为参数要写暴力找就偷懒没写(其实是太困了早早睡觉去了)
考虑第一次询问改为问 \(N\) 个 \(B\),根据回答可以分为两种情况:
- 回答为 \(0\),此时说明 \(W\in[1,B-1]\),不难发现第二次问 \(B^2\) 个 \(1\) 就可以唯一确定 \(W\);
- 回答不为 \(0\),此时可以解出 \(W\in \displaystyle \left[B\cdot \left(\left\lfloor\frac{N - 1}{r} + 1\right\rfloor \right), B\cdot \left(\left\lfloor\frac{N - 1}{r - 1} + 1\right\rfloor \right) - 1\right]=[L,R]\);
因此暴力枚举所有可能的 \(N,B\),使得 \(N+\max(B^2,2\times (R-L))\) 的值最小即可
取 \(N=11343,B=116\) 得到最大询问长度为 \(24799\le 25000\),可以通过
#include<cstdio>
#include<iostream>
#include<vector>
#include<assert.h>
#define RI register int
#define CI const int&
using namespace std;
const int N=11343,B=116;
int t;
int main()
{
for (scanf("%d",&t);t;--t)
{
printf("? %d ",N);
for (RI i=1;i<=N;++i)
printf("%d%c",B," \n"[i==N]);
fflush(stdout);
int k; scanf("%d",&k);
if (k==0)
{
printf("? %d ",B*B);
for (RI i=1;i<=B*B;++i)
printf("1%c"," \n"[i==B*B]);
fflush(stdout); scanf("%d",&k);
for (RI W=1;W<B;++W)
if ((B*B+W-1)/W==k)
{
printf("! %d\n",W); fflush(stdout);
break;
}
} else
{
int L=-1,R=-1;
for (RI W=B;W<=100000;++W)
if ((N+(W/B)-1)/(W/B)==k)
{
if (L==-1) L=W; R=W;
}
if (L==R)
{
printf("! %d\n",L); fflush(stdout);
continue;
}
printf("? %d ",2*(R-L));
for (RI i=R-L;i>=1;--i)
printf("%d %d%c",L,i," \n"[i==1]);
fflush(stdout); scanf("%d",&k);
printf("! %d\n",R-(k-(R-L))); fflush(stdout);
}
}
return 0;
}
Postscript
最后事实证明打 CF 对 NJU 的机试也并没有什么太大的帮助

浙公网安备 33010602011771号