赞
踩
直接输出两个数的差即可。再判一下无解的情况。
其实思路还挺顺的,首先拿的牌肯定是一段增一段减一段增一段减……的序列,并且 > n >n >n 的开头和 ≤ n \leq n ≤n 的开头两种序列是对称的,我们随便考虑一种最后答案乘以二即可。
下面称 1 1 1 ~ n n n 的数为一堆, n + 1 n+1 n+1 ~ 2 n 2n 2n 为一堆。
为了避免计重,考虑在每个序列最后一张被取走的牌
的那个位置统计答案,设
f
i
,
j
f_{i,j}
fi,j 表示前
i
i
i 位合法的放置方案数,并且
i
i
i 不在的那一堆里还有
j
j
j 个没用过的数。
j j j 这一维存在的意义很明确,就是要用来转移的,先考虑对答案的贡献,分为两种:
f f f 的转移类似,具体可以看代码。
说起来多其实写起来很短,而且还没啥细节:
#include <bits/stdc++.h> using namespace std; #define maxn 610 int n,mod,C[maxn][maxn],fac[maxn]; void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);} int add(int x){return x>=mod?x-mod:x;} int f[maxn][maxn]; int main() { int Te;cin>>Te;while(Te--){ cin>>n>>mod; for(int i=0;i<=2*n;i++){ C[i][0]=1; for(int j=1;j<=i;j++) C[i][j]=add(C[i-1][j]+C[i-1][j-1]); } fac[0]=1; for(int i=1;i<=2*n;i++) fac[i]=1ll*fac[i-1]*i%mod; for(int i=0;i<=2*n;i++) for(int j=0;j<=n;j++) f[i][j]=0; f[0][n]=1; int ans=0; for(int i=0;i<2*n;i++){ for(int j=1;i+j<=2*n&&j<=n;j++){ if(i+j==2*n)add(ans,1ll*f[i][j]*2*n%mod); for(int k=2;k<=j;k++) add(ans,1ll*(i+k)*f[i][j]%mod*C[j][k]%mod*(k-1)%mod*fac[2*n-i-k]%mod); } for(int j=1;i+j<=2*n&&j<=n;j++) for(int k=1;k<=j;k++) if(2*n-i-j<=n) add(f[i+k][2*n-i-j],1ll*f[i][j]*C[j][k]%mod); } ans=2ll*ans%mod; cout<<ans<<'\n'; } }
手玩一下发现其实只有全 0 0 0 或全 1 1 1 的矩阵才是满足要求的。
考虑一个比较粗略的证明:
首先 0 ≤ r i , c j ≤ 2 n − 1 0\leq r_i,c_j\leq 2^n-1 0≤ri,cj≤2n−1
假如第一列全是 1 1 1,那么 max ( c j ) = 2 n − 1 \max(c_j) =2^n-1 max(cj)=2n−1,则 min ( r i ) = 2 n − 1 \min(r_i)=2^n-1 min(ri)=2n−1,即所有行都是全 1 1 1。
假如第一列存在一个 0 0 0 且不在第一行,那么 max ( c j ) ≥ 2 n − 1 , min ( r i ) < 2 n − 1 \max(c_j)\geq 2^{n-1},\min(r_i)<2^{n-1} max(cj)≥2n−1,min(ri)<2n−1,与条件不符,所以不可能出现。
所以第一列要是有 0 0 0,第一行必然有一个,此时 min ( r i ) < 2 n − 1 \min(r_i)<2^{n-1} min(ri)<2n−1,要使 max ( c j ) < 2 n − 1 \max(c_j)<2^{n-1} max(cj)<2n−1,则第一行不能有 1 1 1。
由此可知,第一行全是 0 0 0,所以 min ( r i ) = 0 \min(r_i)=0 min(ri)=0,故 max ( c j ) = 0 \max(c_j)=0 max(cj)=0,即每一列都全是 0 0 0。
证毕。
说着有点儿绕,总结起来就是:
所以考虑能不能将整个矩阵变成全 0 / 1 0/1 0/1 的矩阵。两种思路是一样的,就将变全 0 0 0 的吧。
首先每一行每一列至多翻转一次,重复翻转没意义。于是可以枚举第一行是否翻转,操作后假如第一行还有 1 1 1,那么只能翻转他所在的这一列来消除掉这个 1 1 1。接下来为了不改变第一行已经合法的状态,所以不能再翻转列了,看下面每一行需不需要翻转即可,假如有一行既有 1 1 1 也有 1 1 1 则无解。
其实是个很贪心很暴力的简单思路,代码如下:
#include <bits/stdc++.h> using namespace std; #define maxn 2010 #define inf 999999999 int n; char s[maxn][maxn]; bool line[maxn],col[maxn]; int get(int x,int y){ int re=s[x][y]-'0'; return (re^line[x]^col[y]); } int solve(){ int re=inf; for(int line1=0;line1<=1;line1++){ memset(line,false,sizeof(line)); memset(col,false,sizeof(col)); line[1]=line1; int tot=line1; for(int i=1;i<=n;i++) if(get(1,i))col[i]=true,tot++; for(int i=2;i<=n;i++) if(get(i,1))line[i]=true,tot++; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(get(i,j))tot=inf; re=min(re,tot); } return re; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%s",s[i]+1); int ans=inf; ans=min(ans,solve()); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(s[i][j]=='1')s[i][j]='0'; else s[i][j]='1'; ans=min(ans,solve()); if(ans>=2*n)puts("-1"); else printf("%d",ans); }
其实就是问 是否
dfs顺序如何变化整张图每个点的深度都不变
。
先跑一次bfs跑出每个点的深度,将图分层,考虑不在bfs树上的几种边 ( u , v ) (u,v) (u,v):
所以其实这题难点是会不会支配树……然而我还不会,但是上网膜了个板子魔改一下之后居然过了。
代码参考性也许不是很大:
#include<bits/stdc++.h> using namespace std; #define maxn 500010 #define cn getchar template<class TY>void read(TY &x){ x=0;int f1=1;char ch=cn(); while(ch<'0'||ch>'9'){if(ch=='-')f1=-1;ch=cn();} while(ch>='0'&&ch<='9')x=x*10+(ch-'0'),ch=cn(); x*=f1; } template<class TY>void write2(TY x){ if(x>9)write2(x/10); putchar(x%10+'0'); } template<class TY>void write(TY x){ if(x<0)putchar('-'),x=-x; write2(x); } int n,m,dfn[maxn],id[maxn],tot,dep[maxn]; int ffa[maxn],fa[maxn],semi[maxn],val[maxn],du[maxn],ff[25][maxn]; queue<int>q,q1; vector<int>cg[maxn]; struct node{ vector<int>edge[maxn]; void add(int u,int v){ edge[u].push_back(v); } void clear(int n){ for(int i=1;i<=n;i++) edge[i].clear(); } }a,b,c,d; void dfs(int x,int f){ dfn[x]=++tot,id[tot]=x,ffa[x]=f,c.add(f,x); for(int i=0;i<a.edge[x].size();++i) if(!dfn[a.edge[x][i]])dfs(a.edge[x][i],x); } int find(int x){ if(x==fa[x]) return x; int tmp=fa[x];fa[x]=find(fa[x]); if(dfn[semi[val[tmp]]]<dfn[semi[val[x]]]) val[x]=val[tmp]; return fa[x]; } int LCA(int x,int y){ if(dep[x]<dep[y]) swap(x,y); int d=dep[x]-dep[y]; for(int i=20;i>=0;i--) if(d&(1<<i)) x=ff[i][x]; for(int i=20;i>=0;i--) if(ff[i][x]^ff[i][y]) x=ff[i][x],y=ff[i][y]; return x==y?x:ff[0][x]; } inline void tarjan(){ for(int i=n;i>=2;--i){ if(!id[i]) continue; int x=id[i],res=n; for(int j=0;j<b.edge[x].size();++j){ int v=b.edge[x][j]; if(!dfn[v]) continue; if(dfn[v]<dfn[x]) res=min(res,dfn[v]); else find(v),res=min(res,dfn[semi[val[v]]]); } semi[x]=id[res],fa[x]=ffa[x],c.add(semi[x],x); } for(int x=1;x<=n;x++) for(int i=0;i<c.edge[x].size();++i) du[c.edge[x][i]]++,cg[c.edge[x][i]].push_back(x); for(int x=1;x<=n;x++) if(!du[x]) q.push(x); while(!q.empty()){ int x=q.front();q.pop();q1.push(x); for(int i=0;i<c.edge[x].size();++i) if(!--du[c.edge[x][i]]) q.push(c.edge[x][i]); } while(!q1.empty()){ int x=q1.front(),f=0,len=cg[x].size();q1.pop(); if(len) f=cg[x][0]; for(int j=1;j<len;j++) f=LCA(f,cg[x][j]); ff[0][x]=f,dep[x]=dep[f]+1,d.add(f,x); for(int p=1;p<=20;p++) ff[p][x]=ff[p-1][ff[p-1][x]]; } } int u[maxn],v[maxn]; int fid[maxn],ID,con[maxn]; void dfs2(int x){ fid[x]=++ID; for(int y:d.edge[x]) dfs2(y); con[x]=ID; } int mydep[maxn],myq[maxn],st,ed; void mybfs(){ myq[st=ed=1]=n;mydep[n]=1; while(st<=ed){ int x=myq[st++]; for(int y:a.edge[x]) if(!mydep[y]){ mydep[y]=mydep[x]+1; myq[++ed]=y; } } } int main() { int T;read(T);while(T--){ read(n);read(m); for(int i=1;i<=n;i++){ fa[i]=val[i]=semi[i]=i; dfn[i]=id[i]=dep[i]=ffa[i]=du[i]=mydep[i]=0; cg[i].clear(); }tot=0; for(int i=1;i<=m;i++){ read(u[i]);read(v[i]); u[i]=n-u[i]+1;v[i]=n-v[i]+1; a.add(u[i],v[i]),b.add(v[i],u[i]); } // root: n dfs(n,0);tarjan(); ID=0;dfs2(n);mybfs(); bool ans=true; for(int i=1;i<=m;i++) if(mydep[u[i]]+1!=mydep[v[i]]){ if(fid[u[i]]>=fid[v[i]]&&fid[u[i]]<=con[v[i]]); else {ans=false;break;} } puts(ans?"Yes":"No"); a.clear(n);b.clear(n);c.clear(n);d.clear(n); } }
当时一眼就想到了马拉车,但是没细想,开玩笑说了个是不是什么二维马拉车的神奇科技……
其实仔细考虑马拉车的话思路是很简单的,考虑每一行,枚举上下匹配对少列,然后直接跑马拉车,每次比较就是比较两段列,这个用哈希整一下就行。
代码晚点儿补……
n = 1 n=1 n=1 的时候特判一下, n = 2 n=2 n=2 的时候根据哥德巴赫猜想来整就行, n = 3 n=3 n=3 其实一样,先判断总和是否大于等于 2 n 2n 2n,是的话就一定可以,假设除了 1 , 2 1,2 1,2 位置全放 2 2 2,剩下的总和是偶数那么就成功了,如果不是偶数,那么就把三号位变成 3 3 3,总和就变成偶数了。
代码如下:
#include <bits/stdc++.h> using namespace std; bool prime(long long x){ for(long long i=2;i*i<=x;i++) if(x%i==0)return false; return true; } int main() { int n;long long sum=0; cin>>n; for(int i=1;i<=n;i++){ int x;cin>>x; sum+=x; } if(sum<2*n)puts("No"); else if(n==1){ if(prime(sum))puts("Yes"); else puts("No"); }else if(n==2){ if(sum&1){ if(prime(sum-2))puts("Yes"); else puts("No"); }else puts("Yes"); }else{ puts("Yes"); } }
沿着路径走的过程肯定是贪心的,能不换颜色就不换颜色,但是每次要换什么颜色呢?只需要将这个点的颜色和后面的点取交集,尽可能往后走,直到交集为空。
于是问题变成了如何快速进行一条路径的贪心,其实这个贪心并不好拆成两段贪并且合并,所以考虑在lca处枚举一个颜色,然后看看这个颜色最多能往 x , y x,y x,y 处延伸到哪儿,比如 x x , y y xx,yy xx,yy,那么现在就拆成了 x → x x x \to xx x→xx 和 y → y y y\to yy y→yy 的两段贪心。
一段子孙到祖先的贪心时很好求的,每个点向上二分一下找到最远能延伸到的点,然后倍增跳一下就求出步数了。
另外有个小优化,就是不用每次枚举之后都重新倍增跳,只需要一开始跳一次,跳到再跳一次就会跨过lca
的位置就可以了,利用这个位置的深度和枚举即可求解。
时间复杂度 O ( 60 n log n ) O(60n \log n) O(60nlogn),加上上面的优化时间复杂度其实也不变,但是由于常数可能有点儿大实在是过不去……
代码如下:
#include <bits/stdc++.h> using namespace std; #define maxn 500010 #define inf 1000000 #define ll long long #define cn getchar template<class TY>void read(TY &x){ x=0;int f1=1;char ch=cn(); while(ch<'0'||ch>'9'){if(ch=='-')f1=-1;ch=cn();} while(ch>='0'&&ch<='9')x=x*10+(ch-'0'),ch=cn(); x*=f1; } template<class TY>void write2(TY x){ if(x>9)write2(x/10); putchar(x%10+'0'); } template<class TY>void write(TY x){ if(x<0)putchar('-'),x=-x; write2(x); } int n,m; ll c[maxn]; vector<int> to[maxn]; int sum[maxn][60]; int f[maxn][19],dep[maxn]; void dfs(int x){ for(int i=0;i<60;i++) if(c[x]>>i&1)sum[x][i]++; for(int y:to[x]){ if(y==f[x][0])continue; f[y][0]=x; for(int i=0;i<60;i++) sum[y][i]=sum[x][i]; dep[y]=dep[x]+1; dfs(y); } } int fa[maxn][19]; void getfa(){ for(int i=1;i<=n;i++){ int x=i; for(int j=18;j>=0;j--){ bool tf=false; for(int k=0;k<60;k++) if((c[f[x][j]]>>k&1) && sum[i][k]-sum[f[x][j]][k]==dep[i]-dep[f[x][j]])tf=true; if(tf)x=f[x][j]; } fa[i][0]=x; } for(int j=1;j<19;j++) for(int i=1;i<=n;i++) fa[i][j]=fa[fa[i][j-1]][j-1]; } int getlca(int x,int y){ if(dep[x]>dep[y])swap(x,y); for(int i=18;i>=0;i--) if(dep[f[y][i]]>=dep[x])y=f[y][i]; if(x==y)return x; for(int i=18;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; } int dis(int x,int y){ if(dep[x]>dep[y])swap(x,y); int re=0; for(int i=18;i>=0;i--) if(dep[f[y][i]]>=dep[x]) y=f[y][i],re+=(1<<i); if(x==y)return re; for(int i=18;i>=0;i--) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i],re+=(1<<i+1); return re+2; } struct par{ int x,step; }; par calc(int x,int y){ if(x==y)return (par){y,0}; int re=0; for(int i=18;i>=0;i--) if(dep[fa[y][i]]>dep[x]) y=fa[y][i],re+=(1<<i); if(dep[fa[y][0]]<=dep[x])return (par){y,re+1}; else return (par){y,inf}; } int calc_ans(int x,int y){ int lca=getlca(x,y); if(lca==x)return calc(x,y).step-1; if(lca==y)return calc(y,x).step-1; int re=inf; par xx=calc(lca,x),yy=calc(lca,y); if(xx.step==inf||yy.step==inf)return inf; for(int i=0;i<60;i++) if(c[lca]>>i&1){ int tot=xx.step+yy.step; if(sum[xx.x][i]-sum[lca][i]==dep[xx.x]-dep[lca])tot--; if(sum[yy.x][i]-sum[lca][i]==dep[yy.x]-dep[lca])tot--; re=min(re,tot); } //优化前写法 /*for(int i=0;i<60;i++)if(c[lca]>>i&1){ int xx=x,yy=y; for(int j=18;j>=0;j--){ if(dep[f[xx][j]]>=dep[lca] && sum[f[xx][j]][i]-sum[lca][i]<dep[f[xx][j]]-dep[lca]) xx=f[xx][j]; if(dep[f[yy][j]]>=dep[lca] && sum[f[yy][j]][i]-sum[lca][i]<dep[f[yy][j]]-dep[lca]) yy=f[yy][j]; } if(sum[xx][i]-sum[lca][i]<dep[xx]-dep[lca])xx=f[xx][0]; if(sum[yy][i]-sum[lca][i]<dep[yy]-dep[lca])yy=f[yy][0]; re=min(re,calc(xx,x)+calc(yy,y)); }*/ return re; } int main() { read(n);read(m); for(int i=1;i<=n;i++)read(c[i]); for(int i=1,x,y;i<n;i++){ read(x);read(y); to[x].push_back(y); to[y].push_back(x); } dep[1]=1;dfs(1); for(int j=1;j<19;j++) for(int i=1;i<=n;i++) f[i][j]=f[f[i][j-1]][j-1]; getfa(); for(int i=1;i<=m;i++){ int x,y;read(x);read(y); int ans=calc_ans(x,y)+dis(x,y); if(ans>=inf)puts("-1"); else write(ans),puts(""); } }
题意一开始还没看懂……就是要找几个 1 1 1 ~ n n n 的排列,使得给出的每个偏序关系都在某个排列中存在。
其实最多只需要两个,如果整一个 1 1 1 ~ n n n 再整个 n n n ~ 1 1 1 那么所有偏序关系都有了。
那么就是要判断一下能不能一个排列整完,将偏序关系连边跑一个拓扑就行,有环就是需要两个排列,否则拓扑序就是答案。
代码如下:
#include <bits/stdc++.h> using namespace std; #define maxn 1000010 struct edge{int y,next;}e[maxn]; int first[maxn],et=0; void buildroad(int x,int y){ e[++et]=(edge){y,first[x]}; first[x]=et; } int main() { int n,m;cin>>n>>m; vector<int> du(n+1); for(int i=1;i<=m;i++){ int x,y;cin>>x>>y; buildroad(x,y); du[y]++; } vector<int> sta,ans; for(int i=1;i<=n;i++) if(!du[i])sta.push_back(i); while(sta.size()){ int x=sta.back();sta.pop_back(); ans.push_back(x); for(int i=first[x];i;i=e[i].next){ int y=e[i].y;du[y]--; if(!du[y])sta.push_back(y); } } if(ans.size()==n){ cout<<"1\n"; for(auto i:ans) cout<<i<<' '; }else{ cout<<"2\n"; for(int i=1;i<=n;i++) cout<<i<<' '; cout<<"\n"; for(int i=n;i>=1;i--) cout<<i<<' '; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。