2021ZR noip集训day12
作者:互联网
http://zhengruioi.com/contest/1030
\(51+100+0+0=151\)
T1 没预处理二的次幂直接飞了
A.数树
对于一棵没有边权的树,树上两个点的距离定义为两点间路径经过的边数。
现在告诉你一棵有 \(n\) 个点的树和 \(q\) 个询问,第 \(i\) 个询问给出 \(d_i\),请问树上有多少个点集 \(S\) 满足集合内最远的两个点之间距离为 \(d_i\),因为答案可能太大,请对 \(10^9+7\) 取模。
\(q<n\le 2000\)
考虑枚举点集直径的中心
然后枚举深度,要求这个深度的点至少在两个子树中分别出现至少一个,其他的随便选
对于 \(d_i\) 是奇数可以每条边中间加一个虚点
预处理二的次幂,\(O(n^2)\)
#define mod 1000000007
#define N 4006
#define M 8006
long long power[N];
struct Graph{
int fir[N],nex[M],to[M],tot=1;
inline void add(int u,int v,int flag=1){
to[++tot]=v;
nex[tot]=fir[u];fir[u]=tot;
// if(flag) printf(" %d %d\n",u,v);
if(flag) add(v,u,0);
}
inline void clear(){std::memset(fir,0,sizeof fir);tot=0;}
}G;
int n;
long long all[N],cnt[M][N];
long long allSum[N],cntSum[M][N];
void dfs(int u,long long *cnt,int fa=0,int deep=0){
cnt[deep]+=(u<=n);all[deep]+=(u<=n);
for(int i=G.fir[u];i;i=G.nex[i])if(G.to[i]^fa) dfs(G.to[i],cnt,u,deep+1);
}
long long f[N];
inline void calc(int u,int op){
std::memset(all,0,sizeof all);
for(int i=G.fir[u];i;i=G.nex[i]){
dfs(G.to[i],cnt[i],u,1);
for(int d=1;d<=n<<1;d++) cntSum[i][d]=cntSum[i][d-1]+cnt[i][d];
}
all[0]=allSum[0]=(u<=n);
for(int d=1;d<=n<<1;d++) allSum[d]=allSum[d-1]+all[d];
if(op==1){
for(int d=4;d<n<<1;d+=4){
long long now=power[allSum[d>>1]];
now=(now-power[allSum[(d>>1)-1]]+mod)%mod;
for(int i=G.fir[u];i;i=G.nex[i]){
long long sub=power[allSum[(d>>1)-1]-cntSum[i][(d>>1)-1]]*((power[cntSum[i][d>>1]]-power[cntSum[i][(d>>1)-1]]+mod)%mod)%mod;
now=(now-sub+mod)%mod;
}
f[d>>1]=(f[d>>1]+now)%mod;
}
}
else{
for(int d=2;d<n<<1;d+=4){
long long now=power[allSum[d>>1]];
now=(now-power[allSum[(d>>1)-1]]+mod)%mod;
for(int i=G.fir[u];i;i=G.nex[i]){
long long sub=power[allSum[(d>>1)-1]-cntSum[i][(d>>1)-1]]*((power[cntSum[i][d>>1]]-power[cntSum[i][(d>>1)-1]]+mod)%mod)%mod;
now=(now-sub+mod)%mod;
}
f[d>>1]=(f[d>>1]+now)%mod;
}
}
}
int main(){
// freopen("a1.in","r",stdin);
n=read();
for(int i=1;i<n;i++) G.add(read(),i+n),G.add(read(),i+n);
power[0]=1;for(int i=1;i<=n<<1;i++) power[i]=power[i-1]*2%mod;
for(int i=1;i<=n;i++) calc(i,1);
for(int i=n+1;i<n<<1;i++) calc(i,0);
int q=read();while(q--) writeEN(f[read()]);
return SUC_RETURN;
}
B.下棋
一个 \(n\times n\) 的棋盘上有 \(m\) 个马和 \(k\) 个兵,小正和小睿在玩游戏。
小正先手,小睿后手,每一手可以移动某一个马,移动的规则按照象棋规则(在某一维度上移动 \(2\) 个单位,另一维度移动 \(1\) 个单位)。移动后的马不能移到棋盘外面,也不能和其他马和兵重合。
第一个让局面出现重复的人输掉游戏。两个局面 \(A\) 和 \(B\) 不重复,当且仅当存在某个位置 \((p,q)\),局面 A 的 \((p,q)\) 有棋子,局面 \(B\) 的 \((p,q)\) 没有棋子。注意:两个马之间是没有差别的。
现在告诉你每个兵的位置,而马的位置未知,请问有多少个初始局面能让小正必胜?
\(n\le 15,1\le m\le 2\)
考虑对所有的局面之间的转移关系建图,这样是一个二分图:
- 对于 \(m=1\),可以按照 \(x+y\) 的奇偶性染色
- 对于 \(m=2\),可以按照 \(x+y\) 和 \(p+q\) 的奇偶性是否相同染色
若存在某种最大匹配,使得一个点不是匹配点,则一定不能以他作为起点:先手必定走到一个匹配点上,然后后手走匹配边,先手非匹配边,一定是以匹配边结束从而先手无路可走
于是就变成了二分图最大匹配必须点的问题:https://www.cnblogs.com/suxxsfe/p/15414767.html
点数边数都是 \(O(n^{2m})\),于是复杂度 \(O(n^{3m})\)
#define N 60006
#define M 2000006
struct Graph{
int fir[N],nex[M],to[M],tot=1;
int w[M],fir_[N];
inline void add(int u,int v,int d,int flag=1){
to[++tot]=v;w[tot]=d;
nex[tot]=fir[u];fir[u]=tot;
// if(flag) printf(" %d %d\n",u,v);
if(flag) add(v,u,0,0);
}
inline void clear(){std::memset(fir,0,sizeof fir);tot=0;}
}G,H;
const int di[]={-2,-1,1,2,2,1,-1,-2};
const int dj[]={1,2,2,1,-1,-2,-2,-1};
int n,m,k;
int id[22][22],no[22][22];
inline int check(int i,int j){return i>=1&&j>=1&&i<=n&&j<=n;}
inline int getId(int i,int j){return (i-1)*id[0][0]+j;}
int S,T;
int deep[N];
int left,right,que[N];
inline int bfs(){
std::memset(deep,0,(T+1)*sizeof deep[0]);
left=right=0;
que[0]=S;deep[S]=1;
int u;
while(left<=right){
u=que[left++];
for(int v,i=G.fir[u];i;i=G.nex[i]){
v=G.to[i];
if(deep[v]||!G.w[i]) continue;
deep[v]=deep[u]+1;que[++right]=v;
if(v==T) return 1;
}
}
return 0;
}
int dfs(int u,int now=INT_INF){
if(u==T) return now;
long long res=now;
for(int v,&i=G.fir_[u];i;i=G.nex[i]){
v=G.to[i];
if(deep[v]!=deep[u]+1||!G.w[i]) continue;
int k=dfs(v,lib::min(res,G.w[i]));
if(!k) deep[v]=0;
else G.w[i]-=k,G.w[i^1]+=k,res-=k;
if(!res) break;
}
return now-res;
}
inline int dinic(){
int ans=0;
while(bfs()){
int now;
std::memcpy(G.fir_,G.fir,(T+1)*sizeof G.fir[0]);
while(now=dfs(S)) ans+=now;
}
return ans;
}
inline void build(){
if(m==1){
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!no[i][j]){
if((i+j)&1) G.add(S,id[i][j],1);
else G.add(id[i][j],T,1);
}
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!no[i][j]&&((i+j)&1)){
for(int i_,j_,k=0;k<8;k++){
i_=i+di[k];j_=j+dj[k];
if(!check(i_,j_)||no[i_][j_]) continue;
G.add(id[i][j],id[i_][j_],1);
}
}
}
else if(m==2){
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!no[i][j])for(int x=1;x<=n;x++)for(int y=1;y<=n;y++)if(!no[x][y]&&(x!=i||y!=j)){
if(((i+j)&1)==((x+y)&1)) G.add(S,getId(id[i][j],id[x][y]),1);
else G.add(getId(id[i][j],id[x][y]),T,1);
}
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!no[i][j])for(int x=1;x<=n;x++)for(int y=1;y<=n;y++)if(!no[x][y]&&(((i+j)&1)==((x+y)&1))&&(x!=i||y!=j)){
for(int i_,j_,x_,y_,k=0;k<8;k++){
i_=i+di[k];j_=j+dj[k];x_=x;y_=y;
if(!check(i_,j_)||no[i_][j_]) goto NO;
G.add(getId(id[i][j],id[x][y]),getId(id[i_][j_],id[x_][y_]),1);
NO:
i_=i;j_=j;x_=x+di[k];y_=y+dj[k];
if(!check(x_,y_)||no[x_][y_]) continue;
G.add(getId(id[i][j],id[x][y]),getId(id[i_][j_],id[x_][y_]),1);
}
}
}
}
int color[N],vis[N],noNeed[N];
inline void rebuild(int k){
std::memset(color,0,sizeof color);std::memset(vis,0,sizeof vis);
for(int u,i=G.fir[k?T:S];i;i=G.nex[i]){
u=G.to[i];
if(G.w[i]^k) noNeed[u]=1;
color[u]=1;
for(int v,j=G.fir[u];j;j=G.nex[j]){
v=G.to[j];
if(G.w[j]^k) H.add(u,v,0,0);//no match
else H.add(v,u,0,0);
}
}
}
void dfs2(int u){
if(vis[u]) return;
vis[u]=1;if(color[u]) noNeed[u]=1;
for(int i=H.fir[u];i;i=H.nex[i]) dfs2(H.to[i]);
}
inline void work(){
rebuild(1);//0=left, 1=right
for(int i=G.fir[T];i;i=G.nex[i])if(!G.w[i]) dfs2(G.to[i]);
H.clear();
rebuild(0);
for(int i=G.fir[S];i;i=G.nex[i])if(G.w[i]) dfs2(G.to[i]);
}
int main(){
n=read();m=read();k=read();
for(int x,i=1;i<=k;i++) x=read(),no[x][read()]=1;
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!no[i][j]) id[i][j]=++id[0][0];
if(m==1) S=id[0][0]+1,T=S+1;
else if(m==2) S=id[0][0]*id[0][0]+1,T=S+1;
build();
// printf("%d\n",G.tot);
dinic();
work();
int ans=0;
if(m==1){
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!no[i][j]&&!noNeed[id[i][j]]) ans++;
}
else if(m==2){
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)if(!no[i][j]){
for(int x=1;x<=n;x++)for(int y=1;y<=n;y++)if(!no[x][y]&&!noNeed[getId(id[i][j],id[x][y])]&&(x!=i||y!=j)) ans++;
}
ans>>=1;
}
writeEN(ans);
return SUC_RETURN;
}
C.走路
有一棵 \(n\) 个点的树,树边有边权。
在此基础上,树上被添加了一些重边(添加的边连接的点在原树内也被一条边直接连通)。
给出 \(Q\) 个询问,每个询问的内容是:询问对于一对点 \(p,q\) 从 \(p\) 走到 \(q\) ,不经过重复的点,最多切换多少次边权。路径上,相邻的边如果边权不同,就切换了一次边权。
\(n\le 5\times 10^5,m\le 10^6\)
一个想法是树上倍增处理出 \(u\) 往上 \(2^i\) 会最多切换多少次,但你发现还需要记录两边是啥不然无法合并
这样的话可能的组合非常多就炸了
但是如果两边边权的组合超过三种,则两边都一定会发生切换,所以只记录三种即可
\(O(n\log n)\)
#define N 500006
#define M 2000006
struct Graph{
int fir[N],nex[M],to[M],tot=1;
inline void add(int u,int v,int flag=1){
to[++tot]=v;
nex[tot]=fir[u];fir[u]=tot;
if(flag) add(v,u,0);
}
inline void clear(){__builtin_memset(fir,0,sizeof fir);tot=0;}
}G;
struct Node{
int ans,size,s[3],t[3];
inline void clear(){__builtin_memset(this,0,sizeof(Node));}
inline Node operator + (const Node &o){
if(!size) return o;
if(!o.size) return *this;
Node ret;ret.clear();
for(int i=0;i<size;i++)for(int j=0;j<o.size;j++)if(t[i]^o.s[j]){
ret.s[ret.size]=s[i];ret.t[ret.size]=o.t[j];ret.size++;
ret.ans=ans+o.ans+1;
if(ret.size==3) goto FULL;
}
FULL:
if(!ret.size){
ret.size=1;ret.s[0]=s[0];ret.t[0]=o.t[0];
ret.ans=ans+o.ans;
}
return ret;
}
inline void insert(int o){
if(size==3) return;
s[size]=o;t[size++]=o;
}
inline void operator = (const Node &o){__builtin_memcpy(this,&o,sizeof(Node));}
};
#define MAX 20
Node ans[22][N];
int fa[22][N],deep[N];
std::vector<int>edge[N];
void pre(int u){
for(int v,i=G.fir[u];i;i=G.nex[i]){
v=G.to[i];
if(v==fa[0][u]||fa[0][v]) continue;
fa[0][v]=u;
pre(v);
}
}
void dfs(int u){
deep[u]=deep[fa[0][u]]+1;
for(int v,i=G.fir[u];i;i=G.nex[i]){
v=G.to[i];
if(v==fa[0][u]||deep[v]) continue;
fa[0][v]=u;
for(int o:edge[v]) ans[0][v].insert(o);
for(int j=1;j<MAX;j++){
fa[j][v]=fa[j-1][fa[j-1][v]];
ans[j][v]=ans[j-1][v]+ans[j-1][fa[j-1][v]];
}
dfs(v);
}
}
inline int calc(int x,int y){
if(x==y) return 0;
if(deep[x]<deep[y]) lib::swap(x,y);
Node A,B;A.clear();B.clear();
for(int i=MAX-1;~i;i--)if(deep[fa[i][x]]>=deep[y]) A=A+ans[i][x],x=fa[i][x];
if(x==y) return A.ans;
for(int i=MAX-1;~i;i--)if(fa[i][x]^fa[i][y]) A=A+ans[i][x],B=B+ans[i][y],x=fa[i][x],y=fa[i][y];
A=A+ans[0][x];B=B+ans[0][y];
for(int i=0;i<A.size;i++)for(int j=0;j<B.size;j++)if(A.t[i]^B.t[j]) return A.ans+B.ans+1;
return A.ans+B.ans;
}
int n,m;
int u[M],v[M],w[M];
int main(){
n=read();m=read();
for(int i=1;i<=m;i++){
u[i]=read();v[i]=read();w[i]=read();
G.add(u[i],v[i]);
}
pre(1);
for(int i=1;i<=m;i++){
if(fa[0][u[i]]==v[i]) edge[u[i]].push_back(w[i]);
else edge[v[i]].push_back(w[i]);
}
dfs(1);
int q=read();while(q--){
int x=read(),y=read();
writeEN(calc(x,y));
}
return SUC_RETURN;
}
D.开环
有一串项链,上面有 \(n\) 个珠子,每个珠子上写了一个字母。
请你选择一个位置把项链破开,变成一条链,我们假设破开后的先后顺序和输入的先后顺序都是顺时针。
对于得到的这条链,请你把它分为 \(k\) 个连续的部分,满足这些部分中字典序最大的那个字典序最小。请你输出字典序最大的那个部分。
\(n\le 5\times 10^5\)
标签:fir,noip,int,tot,2021ZR,fa,long,day12,mod 来源: https://www.cnblogs.com/suxxsfe/p/15501399.html