省选模拟1
A. 序列
如果不考虑彩色那么答案就是 \((n-m+1)k^{n-m}\)
可以用总共减去不是彩色的答案,分两种情况
所有颜色都不同
发现答案和颜色出现的顺序没有关系,只和个数有关,所以可以求出所有答案再除一个数来求出给定的序列的答案
那么设 \(f_{i,j}\) 表示长度 \(i\) 的序列最后 \(j\) 个颜色完全不同的非彩色序列方案数
边界 \(f_{0,0}=1\) 转移 \(f_{i,j}\leftarrow f_{i-1,j'}\)
若 \(j>j'+1\) 转移系数为 \(0\)
若 \(j=j'+1\) 转移系数为 \(k-j'\) ,相当于选了一个不同于前面 \(j'\) 的放在了当前这个位置
若 \(j<j'+1\) 转移系数为 \(1\) ,相当于从前面 \(j'\) 不同的里面选了第 \(j\) 个,这样就只有 \(j\) 个不同的了
再设 \(g_{i,j}\) 定义类似为不过要包含连续 \(m\) 个完全不同的颜色
那么对 \(g\) 求和后再除以 \(\frac{k!}{(k-m)!}\) 就是给定序列在非彩色序列里出现的所有次数了
我的理解就是你先在 \(k\) 个数里有顺序的选了 \(m\) 个数,所以原序列里的每种方案就会出现 \(\frac{k!}{(k-m)!}\) 次
有颜色相同
发现答案只和两边最长的不重复颜色序列长度有关,把这个长度分别设为 \(l,r\)
还是用上面的方法求出方案数 边界 \(f_{0,l}=1,g_{0,r}=1\) 这里的 \(f,g\) 和上面的 \(f\) 一样
那么可以枚举这个序列出现的位置再把左右的方案数乘起来得到最后的答案
Code
#include<bits/stdc++.h>
#define int long long
#define rint signed
#define mod 1000000007
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,k,m,siz,ans,l,r;
int a[25010],t[410],fac[410],inv[410];
int f[25010][410],g[25010][410],sumf[25010][410],sumg[25010][410],F[25010],G[25010];
inline void add(int x){siz+=(++t[x]==1);}
inline void del(int x){siz-=(--t[x]==0);}
inline bool check(){for(int i=1;i<=m;i++){if(i>k) del(a[i-k]);add(a[i]);if(siz==k) return 1;}return 0;}
inline bool check2(){
if(m>k) return false;
for(int i=1;i<=k;i++) t[i]=0;
for(int i=1;i<=m;i++){if(t[a[i]]) return false;t[a[i]]++;}
return true;
}
inline int qpow(int x,int k){
int res=1,base=x;
while(k){if(k&1) res=res*base%mod;base=base*base%mod;k>>=1;}
return res;
}
inline void solve(){
f[0][0]=1;int sum=0;
for(int i=1;i<=n;i++) for(int j=k-1;j;j--){
f[i][j]=(sumf[i-1][j]+f[i-1][j-1]*(k-j+1))%mod;
g[i][j]=(sumg[i-1][j]+g[i-1][j-1]*(k-j+1))%mod;
if(j>=m) (g[i][j]+=f[i][j])%=mod;
sumf[i][j]=(sumf[i][j+1]+f[i][j])%mod;
sumg[i][j]=(sumg[i][j+1]+g[i][j])%mod;
}
for(int i=1;i<k;i++) (sum+=g[n][i])%=mod;
ans=(ans-sum*inv[k]%mod*fac[k-m]%mod+mod)%mod;
printf("%lld\n",ans);
}
signed main(){
#ifdef LOCAL
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
freopen("sequence.in","r",stdin);
freopen("sequence.out","w",stdout);
fac[0]=inv[0]=1;for(int i=1;i<=400;i++) fac[i]=fac[i-1]*i%mod,inv[i]=inv[i-1]*qpow(i,mod-2)%mod;
n=read(),k=read(),m=read();
for(int i=1;i<=m;i++) a[i]=read();
if(check()) printf("%lld\n",(n-m+1)*qpow(k,n-m)%mod),exit(0);
ans=(n-m+1)*qpow(k,n-m)%mod;
if(check2()) return solve(),0;
memset(t,0,sizeof(t));for(int i=1;i<=m;i++){if(t[a[i]]) break;t[a[i]]=1;l=i;}
memset(t,0,sizeof(t));for(int i=m;i>=1;i--){if(t[a[i]]) break;t[a[i]]=1;r=i;}r=m-r+1;
f[0][l]=g[0][r]=1;
for(int i=l;~i;i--) sumf[0][i]=1;
for(int i=r;~i;i--) sumg[0][i]=1;
for(int i=1;i<=n;i++) for(int j=k-1;j;j--){
f[i][j]=(sumf[i-1][j]+f[i-1][j-1]*(k-j+1))%mod;
g[i][j]=(sumg[i-1][j]+g[i-1][j-1]*(k-j+1))%mod;
sumf[i][j]=(sumf[i][j+1]+f[i][j])%mod;
sumg[i][j]=(sumg[i][j+1]+g[i][j])%mod;
}
for(int i=0;i<=n;i++) for(int j=k-1;j;j--) (F[i]+=f[i][j])%=mod,(G[i]+=g[i][j])%=mod;
int sum=0;
for(int i=0;i<=n-m;i++) (sum+=F[i]*G[n-i-m])%=mod;
printf("%lld\n",(ans-sum+mod)%mod);
return 0;
}
B. 有向图
一个点是有趣的当且仅当他的 \(dfs\) 树上仅存在树边和返祖边,可以画图理解
这个可以简单的 \(O(n)\) 判断,于是我们就有了朴素的 \(O(n^2)\) 的暴力
继续观察这个 \(dfs\) 树的性质,发现非根节点如果是有趣的那么他的子树内一定仅存在一条翻过他自己的返祖边
而且这个返祖边链接的点必须是有趣的,于是可以根据这个判定所有的点是否有趣
又因为至少 \(20\%\) 的点有趣,这样就保证了暴力找有趣点的复杂度
Code
#include<bits/stdc++.h>
#define int long long
#define rint signed
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int T,n,m,rt;
int head[100010],ver[200010],to[200010],tot;
int c[100010],dep[100010],up[100010],tmp[100010];
bool vis[100010],F[100010],in[100010];
inline void add(int x,int y){ver[++tot]=y;to[tot]=head[x];head[x]=tot;}
inline bool cmp(int x,int y){return dep[x]<dep[y];}
bool dfs(int x){
vis[x]=1,F[x]=1;
for(int i=head[x];i;i=to[i]){
int y=ver[i];
if(!vis[y]){dep[y]=dep[x]+1;if(!dfs(y)) return false;}
else if(!F[y]) return false;
}
F[x]=0;
return true;
}
void dfs2(int x){
up[x]=x;
for(int i=head[x];i;i=to[i]){
int y=ver[i];
if(dep[x]<dep[y]){
dfs2(y);
c[x]+=c[y];if(dep[up[y]]<dep[up[x]]) up[x]=up[y];
}else{
c[x]++,c[y]--;
if(dep[y]<dep[up[x]]) up[x]=y;
}
}
}
inline void solve(){
n=read(),m=read();for(int i=1;i<=n;i++) head[i]=0;tot=0;
for(int i=1,x,y;i<=m;i++){x=read(),y=read();add(x,y);}
while(1){for(int i=1;i<=n;i++) vis[i]=0;dep[rt=rand()%n+1]=1;if(dfs(rt)) break;}
for(int i=1;i<=n;i++) in[i]=0,c[i]=0,tmp[i]=i;in[rt]=1;dfs2(rt);
sort(tmp+1,tmp+1+n,cmp);for(int i=1,x;i<=n;i++){x=tmp[i];if(c[x]==1&&in[up[x]]) in[x]=1;}
for(int i=1;i<=n;i++) if(in[i]) printf("%lld ",i);puts("");
}
signed main(){
#ifdef LOCAL
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
freopen("graph.in","r",stdin);
freopen("graph.out","w",stdout);
T=read();while(T--) solve();
return 0;
}
C. 图形
发现构成的图形要想是凸包那么一定是走完一种边后再走另一种边,任意两种边不能交叉走
又要逆时针走回原点于是只能从 \(1,3\) 象限出发
而从 \(3\) 象限出发的凸包一定可以平移后由从 \(1\) 出发的得到于是只用考虑从 \(1\) 象限出发的凸包
根据构成的凸包,还可以得到如下性质
\(\sum\limits_{x_i>0} x_ic_i \leq m\) 可以画图理解, \(y\) 的也一样
然后就发现 \(|x_i|,|y_i|\leq 4\) 而且 \(n\leq 5\) 也就是说每次选择造成的进位不会很多
于是可以二进制拆分,一次都选 \(2^i\) 个点,假装得出 \(dp\) 定义
\(f_{i,a,b,c,d,s1,s2}\) 分别表示二进制下第 \(i\) 位,\(\sum\limits_{x_i>0}c_ix_i\) 的低位对高位的影响,后面三个类似
\(s1,s2\) 分别表示 \(x,y\) 是否满足小于第 \(i\) 位的 \(m\) 的限制也就是说如果不满足会对下一次考虑是否满足限制造成影响
然后每次分别枚举所有情况的选择方案,再对每一维进行计算累加方案数就行
Code
#include<bits/stdc++.h>
#define int long long
#define rint signed
#define mod 998244353
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
inline int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m,U;
int vx[10],vy[10];
int px[32],py[32],nx[32],ny[32];
int f[32][21][21][21][21][2][2];
signed main(){
#ifdef LOCAL
freopen("in","r",stdin);
freopen("out","w",stdout);
#endif
freopen("shape.in","r",stdin);
freopen("shape.out","w",stdout);
n=read(),m=read();for(int i=1;i<=n;i++) vx[i]=read(),vy[i]=read();
U=(1<<n)-1;f[0][0][0][0][0][0][0]=1;
for(int sta=0;sta<=U;sta++){
for(int i=1;i<=n;i++) if(sta&(1<<(i-1))){
if(vx[i]>0) nx[sta]+=vx[i];else px[sta]-=vx[i];
if(vy[i]>0) ny[sta]+=vy[i];else py[sta]-=vy[i];
}
}
for(int i=0;i<=30;i++) for(int a=0;a<=nx[U];a++) for(int b=0;b<=px[U];b++) for(int c=0;c<=ny[U];c++) for(int d=0;d<=py[U];d++) for(int s1=0;s1<=1;s1++) for(int s2=0;s2<=1;s2++) if(f[i][a][b][c][d][s1][s2]) for(int s=0,_s1,_s2,_a,_b,_c,_d;s<=U;s++){
if((a+b+px[s]+nx[s])&1||(c+d+py[s]+ny[s])&1) continue;
_s1=((s1+((a+nx[s])&1))>(m>>i&1));
_s2=((s2+((c+ny[s])&1))>(m>>i&1));
_a=(a+nx[s])>>1,_b=(b+px[s])>>1,_c=(c+ny[s])>>1,_d=(d+py[s])>>1;
(f[i+1][_a][_b][_c][_d][_s1][_s2]+=f[i][a][b][c][d][s1][s2])%=mod;
}
printf("%lld\n",f[31][0][0][0][0][0][0]-1);
return 0;
}
总结
这场很离谱,发现 \(T1\) 要把彩色的序列和给定的序列拼在一起考虑的性质,也想到和左右最长的长度有关
最后还要拼起来用总方案减去,但就是不对,跟正解以及相似了但就是没写出了
这也导致了 \(T2,T3\) 没时间写, \(T2\) 看了两眼发现可能和 \(dfs\) 树有点关系,但是迫于时间没有细想
\(T3\) 也类似最后因为不会按角度排序所以没法枚举每种选择的数量
然后就 \(T2,T3\) 疯狂横跳,一个暴力也没写出来,死磕战术大失败,感觉要是先看 \(T2\) 的话或许有机会做出来