2023.7.3
A
求把一张无向图的所有边变为有向边,使得各点出度为 \(1\) 的方案数。
答案对 \(998244353\) 取模。
\(1\le n,m\le 2\times 10^5\).
首先一定有 \(n=m\),然后环上的边有两种取法。
把环数找出来,然后判断这个连通块中 \(E\) 是否等于 \(2V\).
#include<bits/stdc++.h>
#define ll long long
#define N 200010
#define Mod 998244353
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,m;
int head[N],ver[N<<1],nxt[N<<1],tot,go[N<<1];
int edge,vertex;
void add(int u,int v){
nxt[++tot]=head[u];
ver[tot]=v;
head[u]=tot;
}
bool vis[N];
int ans=1;
void dfs(int u,int pre){
vis[u]=true,vertex++;
for(int i=head[u],v;i;i=nxt[i]){
edge++;
if((v=ver[i])==pre)continue;
if(!vis[v])go[i]=true,dfs(v,u);
}
}
int main(){
n=read(),m=read();
for(int u,v;m;m--){
u=read(),v=read();
add(u,v),add(v,u);
}
for(int i=1;i<=n;i++){
if(vis[i])continue;
edge=vertex=0,dfs(i,0);
if(edge!=vertex*2){
puts("0");
return 0;
}
}
for(int i=1;i<=tot;i+=2){
if(!go[i]&&!go[i+1])(ans<<=1)%=Mod;
}
printf("%d\n",ans);
return 0;
}
B
一个排列 \(P\) 的价值为其形成的所有环长的 \(\rm lcm\) 的 \(k\) 次方。
给定 \(n,k\),求 \(\sum_{P}val(P)\).
答案对 \(998244353\) 取模。
\(2\le n\le 50\),\(1\le k\le 10^4\).
思考 \(\{a_1,a_2,\dots,a_n\}\) 为环长为 \(i\) 的环数。
合法的 \(\{a\}\) 有 \(204226\) 种。
对于每个 \(\{a\}\) 计算其价值。
即
里面有一坨东西记为
这个东西可以递推。
\(\rm dfs\) 里面记录一下当前价值和 \(\rm lcm\) 即可。
#include<bits/stdc++.h>
#define ll long long
#define N 55
#define Mod 998244353
using namespace std;
ll qpow(ll k,ll b,ll p){
ll ret=1;
while(b){
if(b&1)(ret*=k)%=p;
(k*=k)%=p,b>>=1;
}
return ret;
}
ll n,k,ans;
ll fac[N],pw[N],C[N][N],f[N][N];
ll inv[N];
void init(){
fac[0]=C[0][0]=inv[0]=1;
for(int i=1;i<=n;i++){
C[i][0]=C[i][i]=pw[i]=1;
fac[i]=fac[i-1]*i%Mod,inv[i]=qpow(fac[i],Mod-2,Mod);
for(int j=1;j<=k;j++)
(pw[i]*=i)%=Mod;
for(int j=1;j<i;j++)
C[i][j]=(C[i-1][j-1]+C[i-1][j])%Mod;
}
for(int j=1;j<=n;j++){
f[0][j]=1;
for(int i=1;i<=n/j;i++)
f[i][j]=f[i-1][j]*C[i*j][j]%Mod*fac[j-1]%Mod;
}
for(int j=1;j<=n;j++)
for(int i=1;i<=n/j;i++)
(f[i][j]*=inv[i])%=Mod;
}
void dfs(ll lst,ll cur,ll lcm,ll sum){
if(!lst){
(ans+=sum)%=Mod;
return;
}
if(cur==n+1||lst<cur)return;
dfs(lst,cur+1,lcm,sum);
ll add=cur/__gcd(lcm,cur);
for(int i=1;i<=n/cur;i++)
dfs(lst-cur*i,cur+1,lcm*add,sum*pw[add]%Mod*C[lst][cur*i]%Mod*f[i][cur]%Mod);
}
int main(){
cin>>n>>k,init();
dfs(n,1,1,1);
printf("%lld\n",ans);
return 0;
}
C
开题速度真的块。
问将 \(n\) 个数分为若干组且极差的总和不超过 \(m\) 的方案数。
\(1\le n\le 200\),\(1\le m\le 1000\).
先排序,正常思路分 \(4\) 种情况:
-
加到一个组中且该组未闭合,贡献 \(0\).
-
加到一个组中且使该组闭合,贡献 \(a_i\).
-
新建一个未闭合的组,贡献 \(-a_i\).
-
新建一个闭合的组,贡献 \(0\).
记 \(f_{i,j,k}\) 为前 \(i\) 个数有 \(j\) 个未闭合区间,总极差为 \(k\) 的方案数:
有负下标且状态数为 \(O(n^2\sum a_i)\),不太行。
费用提前计算,令 \(d=a_i-a_{i-1}\),此时的 dp 方程为
时间复杂度 \(O(n^2m)\).
#include<bits/stdc++.h>
#define ll long long
#define N 205
#define M 1010
#define Mod 1000000007
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,m,a[N],f[N][N][M];
int ans;
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)
a[i]=read();
sort(a+1,a+1+n);
f[1][1][0]=f[1][0][0]=1;
for(int i=2;i<=n;i++)
for(int j=0;j<=i;j++)
for(int k=0,d;k<=m;k++){
d=a[i]-a[i-1];
if(k-j*d>=0)
(f[i][j][k]+=1ll*f[i-1][j][k-j*d]*(j+1)%Mod)%=Mod;
if(k-(j+1)*d>=0)
(f[i][j][k]+=1ll*f[i-1][j+1][k-(j+1)*d]*(j+1)%Mod)%=Mod;
if(k-(j-1)*d>=0&&j)
(f[i][j][k]+=f[i-1][j-1][k-(j-1)*d])%=Mod;
}
for(int i=0;i<=m;i++)
(ans+=f[n][0][i])%=Mod;
printf("%d\n",ans);
return 0;
}
D
有 \(3n\) 张卡片,数字值域为 \(\lbrack 1,n\rbrack\)。
-
每次取最左端的 \(5\) 张,任意调整顺序并删除最左边的 \(3\) 张,若这 \(3\) 张相等获得 \(1\) 贡献。
-
若最后剩下的 \(3\) 张相等,获得 \(1\) 贡献。
输出最大的贡献。
\(1\le n\le 2000\).
\(34\) 分做法是设 \(f_{i,j,k}\) 为第 \(i\) 次删除时左边两张是 \(j\) 和 \(k\) 的最大贡献。
转移有 \(10\) 种,时间复杂度 \(O(10\times n^3)\).
思考如何减少状态转移数。
- \(x\space y\space a\space a\space a\)
直接不处理,全部 \(f+1\).
- \(x\space y\space a\space b\space c\)
然后你发现确定了 \(a,b,c\) 之后在原数组上新增的状态只会有 \(O(3n)\) 种。
所以不要滚动。