Preface
恭迎废物hl666终于回到了他忠诚的蓝名之位
本来这场25min切完前三题而且没挂的时候感觉状态不错的,然后D被自己的一个假做法坑了1h
最后ztc想出了大概的构造方法后已经来不及写了,一些细节没考虑好直接爆炸
本来当时就有弃D开E的想法的,但可以E的题意在公告出来之前就已经读错了,后面出了公告的时候已经决定死磕D了就没管了
不然我是打算先写E的暴力找规律的,因为前面的假题意发现连暴力都写不来
嘛不管怎么说还是自己太菜了,滚回去也是正常的说,只能再多加练习了的的说
A. A-characteristic
SB题,枚举\(1\)的个数判断即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int t,n,k,a[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; int ret=-1; for (scanf("%d%d",&n,&k),i=0;i<=n;++i)
if (i*(i-1)/2LL+(n-i)*(n-i-1)/2LL==k) { ret=i; break; }
if (!~ret) { puts("NO"); continue; }
for (puts("YES"),i=1;i<=ret;++i) putchar('1'),putchar(' ');
for (i=ret+1;i<=n;++i) printf("-1"),putchar(' '); putchar('\n');
}
return 0;
}
B. Sort with Step
SB题,不难发现此时的交换只能在下标对\(k\)取模的同余系中进行
因此把每个数初始时所在的下标和最后要换到的位置的下标对\(k\)取模的值比较下,统计出不相同的对数\(cnt\)
显然若\(cnt>2\)则一定无解,否则\(cnt=0\)则直接合法,\(cnt=2\)则交换一次后有解
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,k,x,pos[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i)
scanf("%d",&x),pos[x]=i; int cur=0,x,y;
for (i=1;i<=n;++i) if (pos[i]%k!=i%k) ++cur;
if (cur>2) { puts("-1"); continue; }
if (cur==0) { puts("0"); continue; }
puts("1");
}
return 0;
}
C. Strongly Composite
一眼题,首先考虑先把所有数都质因数分解,求出每个质因数\(p_i\)的次数\(c_i\)
不难发现贪心地把\(c_i\)拿出两个组成一个数是合法且最优的,然后此时我们就剩下了一些次数为\(1\)余量
考虑不同的质数之间至少要\(3\)个才能组成一个合法的数,因此把剩下的三三分组即可
由于多组数据下清空数组很麻烦,所以建议直接开unordered_map
#include<cstdio>
#include<iostream>
#include<unordered_map>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e7+5;
int t,n,x,pri[N],cnt; bool vis[N]; unordered_map <int,int> rst;
inline void init(CI n)
{
RI i,j; for (i=2;i<=n;++i)
{
if (!vis[i]) pri[++cnt]=i;
for (j=1;j<=cnt&&i*pri[j]<=n;++j)
if (vis[i*pri[j]]=1,i%pri[j]==0) break;
}
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t),init(1e7);t;--t)
{
RI i,j; for (scanf("%d",&n),rst.clear(),i=1;i<=n;++i)
{
for (scanf("%d",&x),j=1;j<=cnt&&pri[j]*pri[j]<=x;++j)
if (x%pri[j]==0)
{
while (x%pri[j]==0) ++rst[pri[j]],x/=pri[j];
}
if (x>1) ++rst[x];
}
int ret=0,left=0; for (auto [u,v]:rst) ret+=v/2,left+=v%2;
printf("%d\n",ret+left/3);
}
return 0;
}
D. Unique Palindromes
构造题腐乳闪总出列,再扯什么构造题精通我就是弱智,这下被狠狠地拷打了
首先我们要发现两个重要性质:长度为\(k\)的串最多只能有\(k\)个本质不同的回文串;只用两种字符时对于长度为\(k\)的串本质不同的回文串个数永远是\(k\)
但是我们会发现如果有三种字符结果就大不一样了,\(abcabcabc\cdots\)这个串不管长度为多少答案都是\(3\)
然后我们就由此想到一种构造原则,考虑构造的方案中回文串只能由一种字符构成,即等价于任意一种字符中间都要有两种其它的字符
再注意到一点就是\(k\)的值很小,小到比字符集大小还小,因此很容易想到每次新引入一种字符来统计次数,然后就有了大致的做法
先用\(abc\)三种字符填充第一个限制,这里可以让某个前缀字符出现次数多点来得到更多的答案,比如\(x_1=6,c_1=4\)就有构造\(aaaabc\)
然后在后面我们考虑把\(abc\)当作分隔符来用,先填充一段\(c_i-c_{i-1}\)长度的新字符后,后面就用\(abc\)的循环来填即可
不过要注意上面所提的那个要求,因此要判断下以哪个字符开头循环,具体实现可以看代码
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,M=25;
int T,n,k,x[M],c[M]; char s[N],t[3];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&T);T;--T)
{
RI i,j; scanf("%d%d",&n,&k);
for (i=1;i<=k;++i) scanf("%d",&x[i]);
for (i=1;i<=k;++i) scanf("%d",&c[i]);
bool flag=1; for (i=1;i<=k&&flag;++i) if (x[i]<c[i]) flag=0;
if (!flag) { puts("NO"); continue; }
auto solve=[&](CI l,CI r)
{
for (RI i=0;i<=r-l;++i) s[l+i]=t[i%3];
};
for (i=1;i<=c[1]-2;++i) s[i]='a';
t[0]='b'; t[1]='c'; t[2]='a'; solve(c[1]-1,x[1]);
for (i=2;i<=k;++i)
{
int dx=x[i]-x[i-1],dc=c[i]-c[i-1];
if (dx<dc) { flag=0; break; }
if (!dc)
{
auto find=[&](CI pos,const char& ch)
{
for (RI i=pos;i;--i) if (s[i]!=ch) return i;
};
if (s[x[i-1]]!='a'&&s[find(x[i-1]-1,s[x[i-1]])]!='a')
t[0]='a',t[1]='b',t[2]='c'; else
if (s[x[i-1]]!='b'&&s[find(x[i-1]-1,s[x[i-1]])]!='b')
t[0]='b',t[1]='c',t[2]='a'; else
t[0]='c',t[1]='a',t[2]='b';
if (s[x[i-1]]==t[1]) swap(t[1],t[2]);
solve(x[i-1]+1,x[i]); continue;
}
for (j=1;j<=dc;++j) s[x[i-1]+j]='c'+i-1;
if (s[x[i-1]]!='a')
t[0]='a',t[1]='b',t[2]='c'; else
if (s[x[i-1]]!='b')
t[0]='b',t[1]='c',t[2]='a'; else
t[0]='c',t[1]='a',t[2]='b';
if (s[x[i-1]]==t[1]) swap(t[1],t[2]);
solve(x[i-1]+dc+1,x[i]);
}
if (!flag) { puts("NO"); continue; }
for (puts("YES"),i=1;i<=n;++i) putchar(s[i]); putchar('\n');
}
return 0;
}
E. Removing Graph
很经典的博弈模型,一眼对不同大小的环求SG函数,但是前面题意看假了暴力都写不来苦路西
后面发现每次只能取一段连续的就可以写暴力了,然后这个的规律其实还是挺好找的说
先说下暴力的写法吧,设\(cycle[x]\)表示长度为\(x\)的环的SG函数,\(chain[x]\)表示长度为\(x\)的链的SG函数,转移有:
- \(cycle[x]=\operatorname{mex}_{l\le i\le r} chain[x-i]\)
- \(chain[x]=\operatorname{mex}_{l\le i\le r,1\le j\le x,i+j-1\le x} chain[j-1]\oplus chain[x-(i+j-1)]\)
暴力求SG函数的代码如下:
#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=35;
int n,l,r,cycle[N],chain[N];
inline int Chain(CI x)
{
if (~chain[x]) return chain[x];
bool vis[N]; memset(vis,0,sizeof(vis));
for (RI i=1;i<=x;++i) for (RI j=l;j<=r;++j)
if (i+j-1<=x) vis[Chain(i-1)^Chain(x-(i+j-1))]=1;
int mex=0; while (vis[mex]) ++mex;
return chain[x]=mex;
}
inline int Cycle(CI x)
{
if (~cycle[x]) return cycle[x];
bool vis[N]; memset(vis,0,sizeof(vis));
for (RI i=l;i<=r;++i) if (i<=x) vis[Chain(x-i)]=1;
int mex=0; while (vis[mex]) ++mex;
return cycle[x]=mex;
}
int main()
{
freopen("CODE.out","w",stdout);
for (l=1;l<=15;++l) for (r=l+1;r<=15;++r)
{
RI i; printf("Case %d %d\n",l,r);
memset(cycle,-1,sizeof(cycle)); memset(chain,-1,sizeof(chain));
for (printf("index "),i=1;i<=30;++i) printf("%-4d%c",i," \n"[i==30]);
for (printf("chain "),i=1;i<=30;++i) printf("%-4d%c",Chain(i)," \n"[i==30]);
for (printf("cycle "),i=1;i<=30;++i) printf("%-4d%c",Cycle(i)," \n"[i==30]);
}
return 0;
}
然后随便截取一段暴力的SG函数就可以发现规律:
- 当\(x\ge l+r\)时,\(cycle[x]=0\)
- 当\(x<l+r\)时,\(cycle[x]=\lfloor \frac{x}{l}\rfloor\)
而关于这个东西是如何推导得出的就有些复杂了,可以去Tutorial看
最后代码就很好写了
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int n,l,r,x,y,sz[N],ret; vector <int> v[N]; bool vis[N];
inline void DFS(CI now)
{
sz[now]=vis[now]=1; for (int to:v[now]) if (!vis[to]) DFS(to),sz[now]+=sz[to];
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; for (scanf("%d%d%d",&n,&l,&r),i=1;i<=n;++i)
scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
auto SG=[&](CI x)
{
if (x>=l+r) return 0; return x/l;
};
for (i=1;i<=n;++i) if (!vis[i]) DFS(i),ret^=SG(sz[i]);
return puts(ret?"Alice":"Bob"),0;
}
F. Random Walk
做不来的期望题捏,现在这类题遇到就寄感觉只能躺好等队友了捏
首先不妨先令\(e(t)=0\),这样推出的式子形式比较统一,最后输出的时候补成\(1\)就行
一个naive的想法就是对于每个点列出转移方程,显然有:
\[e(x)=\sum_{(x,y)\in E} \frac{e(y)}{deg(y)} \]但直接做高斯消元肯定是不可能的,我们考虑寻找一点特殊性
考虑问题的简化版本,对于一条链,起点\(s=1\),终点\(t=n\),它的解该如何构造呢
经过简单地尝试我们很容易得到答案为\(e(1)=n-1,e(2)=2(n-2),e(3)=2(n-3),\cdots,e(n-1)=2,e(n)=0\)
把这个式子调整下就变成了\(e(i)=(n-i)\times deg(i)\),然后考虑怎么把原来树上的问题转化到链上
显然由于树的优秀性质,\(s\)到\(t\)的路径一定是唯一的,然后如果我们找出这条路径后,可以以上面的点为根让剩下的点都在某个有根树中
这么做的意义何在呢,这里就有个性质,对于树上某点\(v\)与其父亲\(p\),一定有\(e(u)=\frac{deg(u)}{deg(p)}\cdot e(p)\),证明如下:
- 若\(u\)为叶子节点,则结论显然成立
- 否则有\(e(u)=\sum_\limits{(u,v)\in E\and v\ne p} \frac{e(v)}{deg(v)}+\frac{e(p)}{deg(p)}=\sum_\limits{(u,v)\in E\and v\ne p} \frac{deg(v)\cdot e(u)}{deg(v)\cdot dep(u)}+\frac{e(p)}{deg(p)}=\frac{deg(u)-1}{deg(u)}\cdot e(u)+\frac{e(p)}{deg(p)}\),则有\(e(u)=\frac{deg(u)}{deg(p)}\cdot e(p)\)
然后我们发现这个式子有一个很好的连乘性质,此时以\(r_i\)为根的子树内所有点\(t\)的答案就是\(e(t)=\frac{deg(t)}{deg(r_i)}\cdot e(r_i)\)
此时我们用前面的式子\(e(i)=(n-i)\times deg(i)\)算出路径上所有点的答案然后再下传到子树即可,经过检验这确实是原方程的解
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod=998244353;
int n,s,t,x,y,pre[N],coef[N],E[N]; bool path[N]; vector <int> v[N];
inline void DFS1(CI now,CI fa=0)
{
if (now==t) return; for (int to:v[now]) if (to!=fa) pre[to]=now,DFS1(to,now);
}
inline void DFS2(CI now,CI fa,CI mul)
{
E[now]=1LL*mul*v[now].size()%mod;
for (int to:v[now]) if (to!=fa) DFS2(to,now,mul);
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; for (scanf("%d%d%d",&n,&s,&t),i=1;i<n;++i)
scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
DFS1(s); path[t]=1; for (int now=t;now!=s;now=pre[now])
coef[pre[now]]=coef[now]+1,path[pre[now]]=1;
for (i=1;i<=n;++i) if (path[i])
{
for (int to:v[i]) if (!path[to]) DFS2(to,i,coef[i]);
E[i]=1LL*coef[i]*v[i].size()%mod;
}
for (++E[t],i=1;i<=n;++i) printf("%d ",E[i]);
return 0;
}
Postscript
今天晚上还有场Div2,必须一雪前耻回到紫名
不过学校的图论专题好像也是凌晨开放,如果到时候清醒的话可以抢几个一血再睡觉的说
标签:868,CI,now,const,int,Codeforces,Div,include,deg From: https://www.cnblogs.com/cjjsb/p/17363690.html