A
相信大家都会,简单讨论一下就好了。
设起点在 ( 0 , 0 ) (0,0) (0,0),然后让 n , m n,m n,m 减一,这样方便些。
不妨设 n ≤ m n\leq m n≤m,那有两种情况:
- n ≤ 1 n\leq 1 n≤1,则向下走一步后,向右走两步然后不断
fn
即可,注意判 m m m 的奇偶性。 - 否则先向下走一步,再向右走一步,接着不断
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} (x−ux−u+y−v),你细品那个组合数就会发现他本质就是这个东西。
但是还有个 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);
}