随便几道水题
Red and Blue Graph ##link
Description
给定n个点m条边的无向图,将每个点染成红色或蓝色,求满足下列条件的染色方案数
- 共k个红点
- 连接不同颜色点的边的数量为偶数
Solution
- 直接想无从下手
- 考虑红点,计算所有红点的度数之和,对于连接两个红点的边ed1,贡献2,连接红蓝点的边ed2,贡献1
- 假设红点度数和为A,ed1有B条,ed2有C条,那么 A=B*2+C
- 发现A和C奇偶性必相同,A为偶数,C无论如何都是偶数,否则无论如何C均为奇数
- 因此题目转化为,选k个红点,使其度数和为偶数的方案数
- 枚举k个红点中度数为奇数的个数i,i个奇数度数红点,k-i个偶数度数红点(i一定是偶数),此时贡献的方案数为C(奇数度数点总数,i) * C(偶数度数点总数,k-i)
- 复杂度 \(\mathcal{O}(n+m)\)
Code
E - Red and Blue Graph
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=2e5+10,mod=998244353;
int n,m,k,ans,ou,d[N],fac[N],fy[N];
inline int add(int x,int y){return (x+=y)>=mod?x-mod:x;}
inline int sub(int x,int y){return x<y?x-y+mod:x-y;}
inline int mul(ll x,int y){return (x*=y)>=mod?x%mod:x;}
int mypow(int a,int x,int ret=1){
for(;x;x>>=1,a=mul(a,a))
if(x&1) ret=mul(ret,a);
return ret;
}
int c(int x,int y){
return x<y||y<0?0:mul(fac[x],mul(fy[y],fy[x-y]));
}
int main(){
scanf("%d%d%d",&n,&m,&k);
while(m--){
int x,y;
scanf("%d%d",&x,&y);
++d[x],++d[y];
}
fac[0]=1;
for(int i=1;i<=n;++i) fac[i]=mul(fac[i-1],i);
fy[n]=mypow(fac[n],mod-2);
for(int i=n;i>=1;--i) fy[i-1]=mul(fy[i],i);
for(int i=1;i<=n;++i) ou+=(d[i]%2==0);
for(int i=0;i<=k&&i<=n-ou;i+=2) ans=add(ans,mul(c(n-ou,i),c(ou,k-i)));
printf("%d\n",ans);
return 0;
}
Sugoroku 3 ##link
Description
- n个节点(1~n),第 \(i\) 个节点上有骰子 \((1 \leq i \leq n-1)\) 骰子上面有数0~a[i],等可能,扔到x,下一步走到i+x,初始在1,问走到n期望扔骰子次数,对998244353取模
- 保证 \(1\leq a[i]\leq n-i\)
- \(2\leq n\leq 2*10^5\)
Solution
- 经典题型了属于是,但长时间没做套路还是不会了
- 开始想正着dp,f[i]定义为从1到i期望步数,但是不管是定义为第一次走到还是最后一次走到i都没办法转移
- 定义为第一次走到无法向后转移,定义为最后一次走无法向自己转移
- 所以套路就是倒着定义,倒着转移,定义f[i]为i走到n期望步数,现在在i就行了,不用管别的,可以向前转移,可以向自己转移
- \(f[i]=\frac{f[i]+\sum\limits_{j=i+1}^{i+a[i]}f[j]}{a[i]+1}+1\),移项得 \(f[i]=\frac{a[i]+1+\sum\limits_{j=i+1}^{i+a[i]}f[j]}{a[i]}\),记录个f后缀和倒着遍历转移一遍就行了,答案为f[1]
- 复杂度 \(\mathcal{O}(nlogn)\)
Code
Sugoroku 3
#include<map>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;
const int N=2e5+10,mod=998244353;
int n,a[N],s[N];
inline int add(int x,int y){return (x+=y)>=mod?x-mod:x;}
inline int dec(int x,int y){return (x<y)?x-y+mod:x-y;}
inline int mul(ll x,int y){return (x*=y)>=mod?x%mod:x;}
int mypow(int a,int x,int ret=1){
for(;x;x>>=1,a=mul(a,a))
if(x&1) ret=mul(ret,a);
return ret;
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;++i) scanf("%d",&a[i]);
for(int i=n-1;i>=1;--i){
int now=mul(add(dec(s[i+1],s[i+a[i]+1]),add(a[i],1)),mypow(a[i],mod-2));
s[i]=add(s[i+1],now);
if(i==1) printf("%d\n",now);
}
return 0;
}
Tournament ##link
Description
- 有 \(2^n\) 个人,编号1~\(2^n\),进行n次比赛,每次比赛淘汰一半人,假设某次比赛时有2M(\(M\geq 1)\)个人, 按编号站,第 \(2i-1\) 个人和第 \(2i\) 个人一组比赛(\(1\leq i\leq M\)),输的淘汰,最后1人胜出
- 给定 \(C_{i,j}\) 第i个人胜了j场比赛得到的钱,规定胜0场得到的钱为0
- 问所有人得到的钱数之和最大值
- \(1\leq n\leq 16\)
- \(1\leq C_{i,j}\leq 10^9\)
Solution
- 因为可能一个人胜的多不如胜的少拿钱多,所以贪心就算了
- 我们发现这个晋级的过程像是线段树从底层合并,每场比赛就是每层的节点合并左右儿子,每个节点都代表一个目前的胜者,而且这棵树是个完全二叉树
- 这样一来我们就算对于每个节点都遍历一遍他的子树中节点,时间也才是 \(NlogN\) 的(N=\(2^n\))
- 可以在这棵线段树上dp了,f[rt][x],对于节点rt所管辖的人,除了目前胜者x之外的人(都领盒饭了)最多拿钱数量
- 定义有了转移就好说了,以x从左子树转移为例,如果左右子树中选出的胜者比了d场比赛
\[f[rt][x]=max_{y\in R}(f[L][x]+f[R][y]+C[y][d])
\]
\[f[rt][x]=f[L][x]+max_{y\in R}(f[R][y]+C[y][d])
\]
- 然而右边加的max是个定值,可以先扫一遍R得到,然后对于L的每个x转移,左右相反同理
- 边界,对于线段树底层节点rt,只管辖一个人x,此节点d=0(x是前0场比赛胜者),f[rt][x]=0
- 根节点编号为1,答案即为 \(max_{x\in 1}(f[1][x]+C[x][n])\)
- 时间和空间复杂度均为 \(\mathcal{O}(NlogN)\)
Code
Tournament
#include<map>
#include<cmath>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define L (rt<<1)
#define R (rt<<1|1)
using namespace std;
const int N=(1<<16)+10;
int mxd,n,c[N][17];
ll mx[N*4];
struct Node{
int x;
ll val;
};
vector<Node>f[N*4];
void dp(int rt,int l,int r,int d){
if(l==r){
f[rt].push_back((Node){l,0});
mx[rt]=0;
return;
}
int mid=(l+r)/2;
dp(L,l,mid,d-1),dp(R,mid+1,r,d-1);
for(Node now:f[L]) f[rt].push_back((Node){now.x,now.val+mx[R]});
for(Node now:f[R]) f[rt].push_back((Node){now.x,now.val+mx[L]});
for(Node now:f[rt]) mx[rt]=max(mx[rt],now.val+c[now.x][d]);
}
int main(){
scanf("%d",&mxd),n=1<<mxd;
for(int i=1;i<=n;++i){
for(int j=1;j<=mxd;++j){
scanf("%d",&c[i][j]);
}
}
dp(1,1,n,mxd);
printf("%lld\n",mx[1]);
return 0;
}
Empty Graph ##link
(这题可真tm是个好题)
...