省选模拟(36-40)
省选模拟36#
1.Alchemy##
汉诺塔问题.
把n个盘子移到3的最优决策一定是把n-1个盘子移到2,把n移到3,然后再把n-1个盘子移到3.
所以递归求解看当前操作到哪个步骤了.
如果有位置不对的就直接了.
2.Algebra##
3.Anarchy##
省选模拟37#
1.奶酪##
直径
题目意思就是求出断开每条边时的两连通块的直径.
这是之前的一道noip模拟.
做法大致是先求出直径边.
然后对直径的s,t在树上\(dfs\),求出子树最长直径.
然后分类讨论一下就好了.
还有一种做法是考虑到子树在dfs序上是一段连续区间.
因此维护dfs序上前缀后缀的直径.
然后合并一下就好了.
还有一种比较麻烦的做法要维护一个点的前三长的链.
2.走##
期望dp+高斯消元
一个浅显的\(dp[i][j][k][d]\)为走了i步到达j点串a匹配了k,串b匹配了b 的距停止行走期望步数.
又忘掉了倒叙求期望.
然后k是跳着转移的.所以要高斯消元.
但由于d不会减少.
所以倒叙枚举d,再进行高斯消元.
3.机##
提答题咕咕
省选模拟38#
1.Inverse##
期望dp
又忘了期望的线性性了.
由于期望具有线性性,考虑每个点对的贡献.
发现有贡献的情况只有\(i<j\&\&a[i]>a[j]\).
设\(f[I][i][j]\)表示操作I次\(i<j\&a\&a[i]>a[j]\)的概率.
转移分翻转区间包含i,j的情况来讨论.
同时包含
包含一个
都不包含
然后发现可以化成前缀和的前缀和的形式.
所以就可以\(O(1)\)转移了.
有个\(trick\)就是对于转移是\(dp[i+j+x][i+j+y]\)的形式.
不是很好前缀和.
不妨设\(f[i][j]=dp[i][i+j]\)
那么\(dp[i+j+x][i+j+y]=f[i+j+x][y-x]\).
那么就又有一维已知,就可以前缀和了.
2.Subsequence##
平衡树+差分表
做过的一道题很类似.
一个简单dp是\(dp[i][j]\)表示考虑到i结尾,长度为j的序列的贡献的最大值.
转移
发现有个取max操作很烦.
拆掉.
发现有个差分形式.
设g是差分数组.
\(1.g[i-1][j]>j*a[i]\)
\(dp[i][j]=dp[i-1][j]\)
\(g[i][j]=g[i-1][j]\)
\(2.g[i-1][j]=j*a[i]\)
\(dp[i][j]=dp[i-1][j-1]+j*a[i]\)
\(g[i][j]=j*a[i]\)
\(3.g[i-1][j]<j*a[i]\)
\(dp[i][j]=dp[i-1][j-1]+j*a[i]\)
\(g[i][j]=g[i-1][j-1]+a[i]\)
发现以上等价于平衡树上插入,区间修改.
3.Convex##
?
由于题目只要求凸包被切成两半的差的绝对值.
因此可以枚举切线的起点,并且找到那个面积最大的未能超过一半的终点.
然后发现这个终点有单调性.
问题变成了如何快速统计出来多个多边形的面积.
这里get到了一种新的求面积的方法.
三角形剖分的那个起点可以是任意位置.
因此如果选择0点的话就是逆时针这个多边形相邻点的坐标(其实是向量了)的叉积和.
对于本题的多边形来说.
只有一个切线不在原多边形上需要特殊考虑.
问题在于咋求若干起点固定,终点连续的多边形面积.
发现由于刚才我们发现的奥妙重重的性质.
使得面积变成了若干个相邻点叉积的求和.
由于求和有着奥妙重重的性质.
使得我们可以对每个叉积考虑它做出的贡献.
发现除了第一个点和第二个点的叉积其余几个叉积贡献都是递减的形式.
于是可以维护叉积的前缀和的前缀和.然后再减去若干倍的前缀和.
这里为了考虑上边那个东西所以减的时候就要少减一个不用减掉第一个点的了.
接下来是起点固定和终点连续的叉积.
发现叉积的公式是$$a_xb_y-a_yb_x$$.
所以维护坐标x值前缀和,y值前缀和就可以做了.
需要注意的是.
由于比较面积不能够取模,因此\(s\)把环倍长后就会爆掉\(ll\),这里可以考虑由于\(s[1~n]=s[n+1,2n]\).
所以可以不用维护后n个的了,\(s[i]=s[n]+s[i-n]\)l来代替就好了.
模拟测试39#
1.gift##
先考虑得知了两个排列后相似度怎么求.
可以发现最优情况一定是相同位置的数连边,然后每个环内部换位置.
那么相似度=n-环数.
问题变成了求环数.
考虑\(A_i=B_i=0\)的部分分就是让你求不同环个数的方案,用第一类斯特林数搞一搞就好了.
考虑一般情况,即有0时候的环数.
首先非0数字组成的环就不用再继续考虑了,在最后输出答案的时候考虑好就行了.
那么对于剩下的情况从\(A_i->B_i\),会形成三种情况:
0->0的环
x->0的链
0->x的链
我们的目的是求出这三种情况瞎组合出来的环的个数.
首先发现一个现象就是除了x->0和0->x需要0->0来作为胶水拼接起来.
其他所有组合都是不需要条件的(包括自己->自己).
所以我们需要单独考虑这个 玩意.
不妨考虑先求出一种自己和自己拼接成环的情况,然后再把剩下的都给0->0.
设n为这种链个数,m为0->0个数.
设\(f[i]\)表示一种链自己恰好组成i个环的方案数.
C和S不用说,就是枚举几条链组成了这i个环.
后面的意思是对于剩下的n-j个链,我不能让它单独存在了.
必须把它附着在其他的该种链上,或者附着在0->0上.
如果说\(m!=0\),那么最后一个这种链也一定会附着到0->0上.
如果说\(m==0\),那就只有在\(j=n\)的情况下在事实上才合法,因此需要特判一下.
此时,我们有了一种链自己组成若干个环的方案,并且保证多余的链都被0->0吸收掉了.
并且0->0的个数没有改变.显然吸收链不会改变个数.
那么现在我们要做的就是把x->0,0->x,0->0卷起来.
其实我们刚才说的0->x+0->0+x->0已经包含在了dp时的扔给0->0操作里了.
这题可以暴力卷的...不用NTT...是我弱智....
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int N=155555;
const int mod=998244353;
inline int rd(register int x=0,register char ch=getchar(),register int f=1){
while(ch<'0'||ch>'9') f=ch=='-'?-1:1,ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar();
return x*f;
}
int n,ptr,bs,oo,xo,ox;
int BIT[N],rev[N],A[N],B[N],e[N],vis[N],deg[N],q[N],g[2][N],S[5005][5005],fac[N],inv[N],finv[N],a[N],al[N];
void swap(int &a,int &b){if(a!=b)a^=b^=a^=b;}
ll mgml(ll a,ll b,ll ans=1){for(;b;b>>=1,a=a*a%mod)if(b&1)ans=ans*a%mod;return ans;}
ll C(ll x,ll y){return x<y||y<0?0:1LL*fac[x]*finv[y]%mod*finv[x-y]%mod;}
ll AA(ll x,ll y){return C(x,y)*fac[y]%mod;}
void NTT(int *a,int len,int opt){
for(int i=0;i<len;++i){
rev[i]=(rev[i>>1]>>1)|((i&1)<<BIT[len]-1);
if(i<rev[i]) swap(a[i],a[rev[i]]);
}int x,y;
for(int i=1;i<len;i<<=1)for(int j=0,wn=mgml(3,(mod-1)/(i<<1));j<len;j+=i<<1)for(int k=0,w=1;k<i;++k,w=1LL*w*wn%mod)x=a[j+k],y=1LL*w*a[j+k+i]%mod,a[j+k]=(x+y)%mod,a[j+k+i]=(x-y+mod)%mod;
if(opt==1) return ;reverse(a+1,a+len);for(int i=0,inv=mgml(len,mod-2);i<len;++i)a[i]=1LL*a[i]*inv%mod;
}
void calc(int n,int m,int *g){
for(int i=0;i<=n;++i) for(int j=i;j<=n;++j) g[i]=(g[i]+1LL*C(n,j)*S[j][i]%mod*(m?AA(n+m-j-1,n-j):n==j)%mod)%mod;
}
int main(){
for(int i=0,j=1;j<N;j<<=1,++i) BIT[j]=i;
S[0][0]=1;for(int i=1;i<5005;++i)for(int j=1;j<=i;++j)S[i][j]=(S[i-1][j-1]+1LL*(i-1)*S[i-1][j])%mod;
fac[0]=fac[1]=inv[0]=inv[1]=finv[0]=finv[1]=1;for(int i=2;i<N;++i)fac[i]=1LL*fac[i-1]*i%mod,inv[i]=1LL*inv[mod%i]*(mod-mod/i)%mod,finv[i]=1LL*finv[i-1]*inv[i]%mod;
ptr=n=rd();for(int i=1;i<=n;++i)A[i]=rd();for(int i=1;i<=n;++i)B[i]=rd();
for(int i=1;i<=n;++i) (A[i]?0:A[i]=++ptr),(B[i]?0:B[i]=++ptr);
for(int i=1;i<=n;++i) e[B[i]]=A[i],deg[A[i]]++,al[A[i]]=1,al[B[i]]=1;
for(int i=1;i<=ptr;++i) if(deg[i]==0&&al[i]){
q[0]=0;for(int j=i;j;j=e[j]) q[++q[0]]=j,vis[j]=1;
if(q[1]>n&&q[q[0]]>n) oo++;
else if(q[1]>n) xo++;
else if(q[q[0]]>n) ox++;
}
for(int i=1;i<=ptr;++i) if(!vis[i]&&al[i]){++bs;for(int j=i;!vis[j];j=e[j]) vis[j]=1;}
calc(xo,oo,g[0]);calc(ox,oo,g[1]);for(int i=0;i<=oo;++i) a[i]=1LL*S[oo][i]*fac[oo]%mod;
int len;for(len=1;len<ptr;len<<=1);
NTT(g[0],len,1);NTT(g[1],len,1);NTT(a,len,1);
for(int i=0;i<len;++i) a[i]=1LL*g[0][i]*g[1][i]%mod*a[i]%mod;
NTT(a,len,-1);
for(int i=0;i<n;++i) printf("%d ",n-i-bs>=0?a[n-i-bs]:0);
return 0;
}
2.girls##
三元环+容斥
直接计算不是很好计算,考虑容斥.
+至少0对不合法-至少1对不合法+至少2对不合法-至少3对不合法
至少三对不合法需要用到\(n\sqrt n\)统计三元环的做法.
#include<cstdio>
#include<vector>
#include<algorithm>
#define ll unsigned long long
using namespace std;
inline int read(register int x=0,register char ch=getchar(),register int f=0){
while(ch<'0'||ch>'9') f=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar();
return f?-x:x;
}
const int N=2e5+10;
ll A,B,C,sa[N],sb[N],sc[N],ans;
int n,d[N],m,id[N];
vector<int> eg[N];
struct node{
int y,next;
}data[N];int h[N],num,fr[N],to[N],vis[N];
inline bool cmp(const int &a,const int &b){
return d[a]<d[b];
}
inline void insert1(int x,int y){
data[++num].y=y;data[num].next=h[x];h[x]=num;
}
int main(){
n=read();m=read();
A=read();B=read();C=read();
for (int i=1;i<=m;++i){
int x=read()+1,y=read()+1;fr[i]=x;to[i]=y;
++d[x];++d[y];eg[x].push_back(y);eg[y].push_back(x);
}
for (int i=1;i<=n;++i) sort(eg[i].begin(),eg[i].end());
for (int i=1;i<=n;++i){
ans+=A*(i-1)*((ll)(n-i)*(n-i-1)/2);
ans+=B*(i-1)*(i-1)*(n-i);
ans+=C*(i-1)*((ll)(i-1)*(i-2)/2);
}
for (int i=1;i<=n;++i){
sa[i]=sa[i-1]+(i-1)*A;
sb[i]=sb[i-1]+(i-1)*B;
sc[i]=sc[i-1]+(i-1)*C;
}
for (int i=1;i<=n;++i){
static ll nm1,nm2;nm1=nm2=0;int sz=eg[i].size()-1;
for (int j=0;j<=sz;++j){
int y=eg[i][j];y<i?++nm1:++nm2;
if(y<i){
ans-=sa[y-1]+B*(y-1)*(y-1)+C*(i-1)*(y-1);
ans+=A*(y-1)*(sz-j)+B*(y-1)*j;
}else{
ans-=A*(y-i-1)*(i-1)+sb[y-1]-sb[i]+C*(y-i-1)*(y-1);
ans-=A*(n-y)*(i-1)+B*(n-y)*(y-1)+sc[n]-sc[y];
ans+=B*(y-1)*(sz-j)+C*(y-1)*(j);
}
}
ans+=A*(i-1)*((ll)nm2*(nm2-1)/2)+B*(i-1)*nm1*nm2+C*(i-1)*((ll)(nm1-1)*(nm1)/2);
}
for (int i=1;i<=m;++i){
int x=fr[i],y=to[i];
if(d[x]==d[y]){
if (x>y) swap(x,y);insert1(x,y);continue;
}
if(d[x]>d[y]) swap(x,y);
insert1(x,y);
}int bl[3];
for (int i=1;i<=m;++i){
int x=fr[i],y=to[i];
for (int j=h[x];j;j=data[j].next) vis[data[j].y]=i;
for (int j=h[y];j;j=data[j].next){
int to=data[j].y;
if(vis[to]==i){
bl[0]=x;bl[1]=y;bl[2]=to;sort(bl,bl+3);
ans-=A*(bl[0]-1)+B*(bl[1]-1)+C*(bl[2]-1);
}
}
}
printf("%llu\n",ans);
return 0;
}
3.strong##
后缀数组
在线,要求
1.在一个串后加字符
2.询问串x在第y次操作后,在当前字符串z中出现的次数.
3.询问本质不同串的个数.
4.给定串T,求T在n个串中最大出现次数.
又看错题了...本来挺简单的...
先上一个广义sam.
1,3,4随便做.
2操作维护一个每次操作后串x的结尾位置就可以随便做了...
LCT链修改,单点修改,单点查询.
可是,这是一份暴跳父亲的代码.
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<vector>
#include<queue>
#include<iostream>
#define ll long long
#define int long long
using namespace std;
const int N=6e5+50;
const int mod=1e9+7;
inline int rd(register int x=0,register char ch=getchar(),register int f=0){
while(ch<'0'||ch>'9') f=ch=='-',ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-48,ch=getchar();
return f?-x:x;
}
int n,o,m,cnt,ptr;
int Lst[25];
int len[N],fa[N],endpos[N][25],ch[N][10],P[N][25];
ll ans[N],num;
vector<int> e;
char s[20000050];
struct Trie{
int ch[10];
vector<int>v;
int p;
}t[N];
int extend(int lst,int c,vector<int> &o){
int p=lst,np=++cnt;
len[np]=len[p]+1;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
if(!p) fa[np]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else{
int nq=++cnt;len[nq]=len[p]+1; fa[nq]=fa[q];fa[q]=fa[np]=nq;
for(int i=0;i<10;++i) ch[nq][i]=ch[q][i];
for(int i=1;i<=n;++i) endpos[nq][i]=endpos[q][i];
for(;p&&ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
num+=len[np]-len[fa[np]];
int x=np;
while(np!=1){
for(int i=0;i<(int)o.size();++i) endpos[np][o[i]]++;
np=fa[np];
}
return x;
}
signed main(){
n=rd();o=rd(); t[ptr=1].p=cnt=1;
for(int i=1;i<=n;++i){
scanf("%s",s+1); int len=strlen(s+1);
int rt=1;
for(int j=1;j<=len;++j){
if(!t[rt].ch[s[j]-'0']) t[rt].ch[s[j]-'0']=++ptr;
rt=t[rt].ch[s[j]-'0']; t[rt].v.push_back(i);
}
Lst[i]=rt;
}
queue<int>q; q.push(1);
while(!q.empty()){
int u=q.front();q.pop(); t[u].v.resize(0);
for(int i=0;i<10;++i) if(t[u].ch[i]) t[t[u].ch[i]].p=extend(t[u].p,i,t[t[u].ch[i]].v),q.push(t[u].ch[i]);
}
for(int i=1;i<=n;++i) P[0][i]=t[Lst[i]].p;
m=rd();
ll lastans=0;
for(int i=1,x,y,z,rt;i<=m;++i){
int opt=rd();
if(opt==1){
x=rd(),y=rd();rt=Lst[x],y=o?(y^lastans)%10:y;
if(!t[rt].ch[y]){
t[rt].ch[y]=++ptr;
e.resize(0); e.push_back(x);
t[t[rt].ch[y]].p=extend(t[rt].p,y,e);
}
else{
int np=t[t[rt].ch[y]].p;
while(np!=1) endpos[np][x]++,np=fa[np];
}
Lst[x]=t[rt].ch[y];
}
else if(opt==2){
x=rd(),y=rd(),z=rd();
printf("%lld\n",lastans=endpos[P[y][x]][z]);
}
else if(opt==3) printf("%lld\n",num);
else{
scanf("%s",s+1);int len=strlen(s+1),it=1;
for(int j=1;j<=len;++j) it=ch[it][s[j]-'0'];
lastans=0;
for(int j=1;j<=n;++j) lastans=max(lastans,0LL+endpos[it][j]);
printf("%lld\n",lastans);
}
for(int j=1;j<=n;++j) P[i][j]=t[Lst[j]].p;
}
return 0;
}
省选模拟40#
1.染色问题##
缩边
本题有个非常特殊的性质.
\(m<=n+5\)
这就一点都给不了给了我们启发.
考虑缩边.
叶子首先可以缩掉,之后答案乘上一个\(k-1\)就行.
但如果是一颗树的话要特判此时是\(k*(k-1)*...*(k-1)\).
对于度数为2的点u,考虑记录下两条边的终点x,y.
删掉u,x.u,y填上x,y这条边.
x,y同色的方案是1种x,u同色*u,v同色,k-1种x,u不同色,u,y不同色.
x,y不同色的方案有1种x,u同,u,y不同,1种u,y同,x,u不同,k-2种x,u不同且u,y不同.
于是把每条边记录前驱后继要删边.
记录这条边两边同色不同色的方案,初始同色是0,不同色是1.
可是还有三元环的情况,这种情况下会出现重边.
可以发现重边的贡献是同色不同色的方案相乘.
还有这种情况下删掉了三元环中的一个后并不能把另外两个当作度数为1的去处理.
所以要特判每个度数为1的点是否在三元环内过,没有再消.
这样我们几乎去除了所有的度数为1,2的点.
好像不是这个啊...
反正最后能解出来n=15左右.
暴力就可以了.
可是暴力也无法解决K==1000的规模.
这就需要用到一个大神的暴力叫做集合划分.
大意是枚举每个点的划分集合然后给这个集合涂上一种相同的颜色.
复杂度是\(Bell\)级别的.
2.IOer##
构造
果然吗...再看一次就又不会了...
这样的做题方式有什么用途吗...又不复习....
发现可以通过构造一种球序列求出方案数来求解答案.
或者你可以像Lnc一样考场爆推组合意义.
3.deadline##
最大流
这种奇怪的数据范围也只能用来打网络流了吧...
问题的实质就是一张左边 n 个点,右边 m 个点的二分图,左边每个点被分为了两类。
先决定右边每个点匹配哪一类的顶点,再求出最大匹配作为 最终结果。
求这些最终结果的最小值。
算法三给我们的启示就是把最大匹配等同于最小覆盖集,那么我们问题被重述为:
把右边每个点和某一种类型的左边顶点连边,求所有的这些二分图子图中最小覆盖集的最小值。
我们使用网络流建图解决这个这个问题。
对于每个右边的顶点 u,我们拆成两个点 u1 ,u2,其中$ u_1 \(向\) u_2$连 一条容量为 1 的有向边。
对于每个左边第一类顶点 v1 ,S 向 v1 连一条容 量为 1 的有向边,再向每个在原二分图中与它相连的顶点 u 所对应的 u1 连 一条流量为 inf 的边。
对于每个左边第二类顶点 v2,我们让 v2 向 T 连一条 权值为 1 的有向边,若右边顶点 u 与 v2 相邻,则让 u 所对应的 u2 与 v2 连边,流量为 inf。
对于这个图跑最小割(也就是最大流)。
如果在最小割中左边第一类顶点 v1 ∈ T ,对应 v1 在覆盖集中。
如 果左边第二类顶点 v2 ∈ S,对应 v2 在覆盖集中。
对于右边的顶点 u,若 u1 ∈ S,u2 ∈ T,那么就是说 u 在覆盖集中,此时不需关心这个顶点被分为了哪一类,因为它对于左边顶点是否选在覆盖集中没有任何限制。
若 u1,u2 ∈ S,就意味着它不在覆盖集中,且要求它与左边第二类顶点连边了。
同样的,若 u1,u2 ∈ T ,就意味着要求它与左边第一类顶点连边。
若 u1 ∈ T,u2 ∈ S,那么这种情况一定可以把 u1 从 T 中移动到 S 中,割的值不会变大。
使用 dinic 算法跑网络流即可得到 100 分。