随便几道水题

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;
}

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;
}

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;
}

(这题可真tm是个好题)
...

posted @ 2022-08-10 12:30  liuzhaoxu  阅读(78)  评论(0编辑  收藏  举报