UOJ Easy Round #3 题解

A

相信大家都会,简单讨论一下就好了。

设起点在 ( 0 , 0 ) (0,0) (0,0),然后让 n , m n,m n,m 减一,这样方便些。

不妨设 n ≤ m n\leq m nm,那有两种情况:

  1. n ≤ 1 n\leq 1 n1,则向下走一步后,向右走两步然后不断fn即可,注意判 m m m 的奇偶性。
  2. 否则先向下走一步,再向右走一步,接着不断fn走到第 n n n 行,然后向右走一步,再不断fn即可。

需要注意的是,第二种情况后面是向右走一步而不是走两步,因为之前已经向右走过一步了,这大概是个小坑点。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

int T,n,m;

int main()
{
    
    
	scanf("%d",&T);while(T--)
	{
    
    
		scanf("%d %d",&n,&m);n--;m--;
		if(n>m)swap(n,m);
		int ans;
		if(n<=1||m<=1){
    
    
			ans=(n==1);
			if(m<=2)ans+=m;
			else ans+=(m-2)/2+2+(m&1);
		}else{
    
    
			ans=(n-1)+2;
			m-=n;
			if(m>0)ans+=(m-1)/2+1+((m-1)&1);
		}
		printf("%d\n",ans);
	}
}

B

这个加一个组合数的操作一眼看上去并不那么直观……但是手玩一下就能发现,其实一个对 ( u , v ) (u,v) (u,v) 的操作相当于给 ( x , y ) (x,y) (x,y) 加上只能向下或向右走,(u,v)走到(x,y)的方案数,即 ( x − u + y − v x − u ) \binom {x-u+y-v} {x-u} (xuxu+yv),你细品那个组合数就会发现他本质就是这个东西。

但是还有个 k k k 的限制,就是只能给 k k k 步以外的 ( x , y ) (x,y) (x,y) 提供贡献,这个可以 dp \text{dp} dp,设 f i , j , k f_{i,j,k} fi,j,k 表示从左上方出发,走到 ( i , j ) (i,j) (i,j) 位置,还要走 k k k 步才能提供贡献的方案数,最后答案就是 f i , j , 0 f_{i,j,0} fi,j,0

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define mod 998244353

int n,m,q,x0;
int rd(){
    
    
	x0=(100000005ll*x0+20150823)%mod;
	return x0/100;
}
int f[310][310][610];
void add(int &x,int y){
    
    x=(x+y>=mod?x+y-mod:x+y);}

int main()
{
    
    
	scanf("%d %d %d %d",&n,&m,&q,&x0);
	for(int i=1;i<=q;i++){
    
    
		int x=rd()%n+1,y=rd()%m+1,k=rd()%(n+m-x-y+1);
		f[x][y][k]++;
	}
	for(int i=1;i<=n;i++){
    
    
		for(int j=1;j<=m;j++){
    
    
			for(int k=0;k<n+m;k++){
    
    
				add(f[i][j][k],f[i-1][j][k+1]);
				add(f[i][j][k],f[i][j-1][k+1]);
			}
			add(f[i][j][0],f[i-1][j][0]);
			add(f[i][j][0],f[i][j-1][0]);
			printf("%d ",f[i][j][0]);
		}
		printf("\n");
	}
}

C

先将所有边建出来造出一张图,然后跑出一棵dfs树来,那么剩下的非树边就都是返祖边了。

将所有返祖边的两端设为关键点造一棵虚树,一条边的权值为两点的深度差,返祖边权值为 1 1 1。考虑枚举断掉哪些边,然后判断一下图是否连通,连通的话就把删掉的边权值乘起来作为方案数,将所有情况的方案数加起来就是答案。

这是官方题解里写到的一个部分分做法,注意判断图是否连通这部分,可以用DZY Loves Chinese II的套路来优化,这样可以在深搜的过程中就判断图是否连通,复杂度大大优化。

但是虚树的写法只能获得 70 70 70 分,可能是写的不优秀,像这样:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 100010
#define pb push_back
#define mod 998244353

int n,k;
struct edge{
    
    int y,z,next;}e[maxn<<1];
int first[maxn],len=0;
void buildroad(int x,int y,int z=0){
    
    e[++len]=(edge){
    
    y,z,first[x]};first[x]=len;}
int f[maxn][18],dep[maxn],vis[maxn];
int id[maxn],idtot=0;
int val[maxn],L[maxn],inv[maxn],now=0;
vector<int> s[maxn],d;
void dfs(int x){
    
    
	vis[x]=1;id[x]=++idtot;
	for(int i=first[x];i;i=e[i].next){
    
    
		int y=e[i].y;if(y==f[x][0])continue;
		if(vis[y]){
    
    
			if(dep[y]>dep[x])continue;
			now++;val[now]=(1<<now);L[now]=1;
			s[x].pb(val[now]);s[y].pb(val[now]);
			d.pb(x);d.pb(y);
		}else{
    
    
			f[y][0]=x;
			dep[y]=dep[x]+1;
			dfs(y);
		}
	}
}
int lca(int x,int y){
    
    
	if(dep[x]>dep[y])swap(x,y);
	for(int i=16;i>=0;i--)if(dep[f[y][i]]>=dep[x])y=f[y][i];
	if(x!=y){
    
    for(int i=16;i>=0;i--)if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];x=f[x][0];}
	return x;
}
bool cmp(int x,int y){
    
    return id[x]<id[y];}
int sta[maxn],t=0;
void add(int x){
    
    
//	printf("add : %d\n",x);
	if(x==1)return;int p=lca(sta[t],x);
	if(p==sta[t])return (void)(sta[++t]=x);
	while(dep[sta[t-1]]>=dep[p]){
    
    
		buildroad(sta[t-1],sta[t],++now);
		L[now]=dep[sta[t]]-dep[sta[t-1]];
		t--;
	}
	if(sta[t]!=p){
    
    
		buildroad(p,sta[t],++now);
		L[now]=dep[sta[t]]-dep[p];
		sta[t]=p;
	}
	sta[++t]=x;
}
int edgeFa[maxn];
void dfs2(int x){
    
    
	for(int i=first[x];i;i=e[i].next){
    
    
		int y=e[i].y;edgeFa[y]=e[i].z;
		dfs2(y);val[edgeFa[x]]^=val[edgeFa[y]];
	}
	for(int i:s[x])val[edgeFa[x]]^=i;
}
int ksm(int x,int y){
    
    int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
struct XXJ{
    
    
	int d[15];
	XXJ(){
    
    memset(d,0,sizeof(d));}
	bool insert(int x){
    
    
		for(int i=12;i>=0;i--){
    
    
			if(x>>i&1){
    
    
				if(d[i])x^=d[i];
				else return d[i]=x,true;
			}
		}
		return false;
	}
};
XXJ cur;int cur_ans=1,ans=0;
bool v[maxn];
void dfs3(int x){
    
    
	if(x>now){
    
    
		ans+=cur_ans;
		if(ans>=mod)ans-=mod;
//		printf("%d : ",cur_ans);for(int i=1;i<=now;i++)printf("%d ",v[i]);printf("\n");
		return;
	}
	v[x]=false;
	dfs3(x+1);
	XXJ past=cur;
	if(cur.insert(val[x])){
    
    
		v[x]=true;
		cur_ans=1ll*cur_ans*L[x]%mod;
		dfs3(x+1);
		cur_ans=1ll*cur_ans*inv[x]%mod;
		cur=past;
	}
}
void init(){
    
    
	for(int j=1;j<=16;j++){
    
    
		for(int i=1;i<=n;i++){
    
    
			f[i][j]=f[f[i][j-1]][j-1];
		}
	}
	sort(d.begin(),d.end(),cmp);
	d.erase(unique(d.begin(),d.end()),d.end());
	sta[t=1]=1;memset(first,0,sizeof(first));len=0;
	for(int i:d)add(i);
	while(t>1){
    
    
		buildroad(sta[t-1],sta[t],++now);
		L[now]=dep[sta[t]]-dep[sta[t-1]];
		t--;
	}
	dfs2(1);
	for(int i=1;i<=now;i++)inv[i]=ksm(L[i],mod-2);
//	for(int i=1;i<=now;i++)printf("%d ",L[i]);printf("\n");
}

int main()
{
    
    
	scanf("%d %d",&n,&k);
	for(int i=1,x,y;i<n+k;i++){
    
    
		scanf("%d %d",&x,&y);
		buildroad(x,y);buildroad(y,x);
	}
	dep[1]=1;dfs(1);init();dfs3(1);
	printf("%d",ans);
}

事实上,相比于虚树,有一个更好的做法:我们的目标是将 v a l val val 相同的边缩在一起,而虚树只能缩一条链,事实上将一个连通块缩在一起也是可以的,直接将 v a l val val 排序加去重即可,并且要记录下原来有多少条边权值为 v a l val val。这样可以使边数更少,于是就可以快速AC了,并且代码还短很多。

代码如下:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 300020
#define pb push_back
#define mod 998244353

int n,k;
struct edge{
    
    int y,z,next;}e[maxn<<1];
int first[maxn],len=0;
void buildroad(int x,int y,int z=0){
    
    e[++len]=(edge){
    
    y,z,first[x]};first[x]=len;}
int val[maxn],now=0,dep[maxn];
void dfs(int x,int fa){
    
    
	for(int i=first[x];i;i=e[i].next){
    
    
		int y=e[i].y;if(y==fa)continue;
		if(dep[y]){
    
    
			if(dep[y]>dep[x])continue;
			now++;val[now+n]=(1<<(now-1));
			val[x]^=val[now+n];val[y]^=val[now+n];
		}else{
    
    
			dep[y]=dep[x]+1;
			dfs(y,x);
			val[x]^=val[y];
		}
	}
}
struct XXJ{
    
    
	int d[12];
	XXJ(){
    
    memset(d,0,sizeof(d));}
	bool insert(int x){
    
    
		for(int i=9;i>=0;i--){
    
    
			if(x>>i&1){
    
    
				if(d[i])x^=d[i];
				else return d[i]=x,true;
			}
		}
		return false;
	}
}b[1010];
int v2[maxn],L[maxn],ans=0;
void dfs2(int x,int cur){
    
    
	if(x>now)return (void)(ans=(ans+cur)%mod);
	b[x]=b[x-1];dfs2(x+1,cur);
	if(b[x].insert(v2[x]))
		dfs2(x+1,1ll*cur*L[x]%mod);
}

int main()
{
    
    
//	freopen("data.txt","r",stdin);
	scanf("%d %d",&n,&k);
	for(int i=1,x,y;i<n+k;i++){
    
    
		scanf("%d %d",&x,&y);
		buildroad(x,y);buildroad(y,x);
	}
	dep[1]=1;dfs(1,0);n+=now;now=0;
	sort(val+2,val+n+1);
	for(int i=2,t=1;i<=n;i++,t++)
	if(i==n||val[i]!=val[i+1])v2[++now]=val[i],L[now]=t,t=0;
	dfs2(1,1);
	printf("%d",ans);
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/109721910