IOI2020集训队作业
[] CF505E
题意:给定 $n$ 个数 $h_{1 \dots n}$。你需要进行 $m$ 轮操作,每轮操作为 $k$ 次修改,每次修改可以选择一个数 $h_i$ 修改为 $\max(h_i - p, 0)$。每轮操作后每个 $h_i$ 将会被修改为 $h_i + a_i$。你需要最小化最终 $h_{1 \dots n}$ 中的最大值。$n \le 10^5$,$m \le 5 \times 10^3$,$k \le 10$。
正难则反,每次找到最容易失败点 $+p$ 即可。
#include<iostream> #include<cstring> #include<cstdio> #include<cstring> #include<vector> #include<queue> #define int long long #define pii pair<int,int> #define pb push_back #define mp make_pair #define fi first #define se second using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=100001; priority_queue<pii> que; int a[MAXN],N,M,k,p,h[MAXN]; int Ans[MAXN],cnt=0; bool check(int Lim){ while(!que.empty()) que.pop();memset(Ans,0,sizeof(Ans));cnt=0; for(int i=1;i<=N;i++) if(Lim-a[i]*M<h[i]) que.push(mp(-(Lim/a[i]),i)); for(int i=1;i<=M;i++){ bool ff=0; for(int j=1;j<=k;j++){ if(que.empty()){ff=1;continue;} int res=-que.top().fi,id=que.top().se;que.pop(); if(res<i) return 0;Ans[id]++; int w=(Lim+p*Ans[id])/a[id]; if(w<M) que.push(mp(-w,id)); ++cnt; }if(ff) break; } for(int i=1;i<=N;i++){ int w=Lim+p*Ans[i]-M*a[i]; if(w>=h[i]) continue; cnt+=((h[i]-w-1)/p)+1; }return cnt<=M*k; } signed main(){ //freopen("1.in","r",stdin); N=read(),M=read(),k=read(),p=read(); for(int i=1;i<=N;i++) h[i]=read(),a[i]=read(); int l=0,r=1e15,res; while(l<=r){ int mid=(l+r)>>1; if(check(mid)) res=mid,r=mid-1; else l=mid+1; }printf("%lld\n",res);return 0; }/* 3 1 2 5 10 10 10 10 15 2 */
[] CF555E
题意:给定一张 $n$ 个点 $m$ 条边的无向图,你需要给图定向,使得 $q$ 个条件,每次可以从 $S_i$ 走到 $T_i$ 。$n,m,q\leq 2\times 10^5$ 。
可以发现一个边双联通分两肯定可以从任意一点走到另一个点,则我们将图缩点以后转化为树上问题。
在树上我们只要给边染色,通过树上差分维护。
#include<iostream> #include<cstring> #include<cstdio> #include<climits> #include<algorithm> #include<queue> #include<vector> #include<map> #define pii pair<int,int> #define mp make_pair #define pb push_back #define fi first #define se second using namespace std; inline int read(){ int f=1,ans=0; char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=2e5+11; int U[MAXN],V[MAXN],N,M,dfn[MAXN],cnt,low[MAXN],head[MAXN],Q; vector<int> vec[MAXN]; struct node{int u,v,nex;}E[MAXN<<1]; void add(int u,int v){E[cnt].u=u,E[cnt].v=v,E[cnt].nex=head[u],head[u]=cnt++;} map<pii,bool> Ma,Ma1; void tarjan(int u,int id){ dfn[u]=low[u]=++dfn[0]; for(int i=head[u];i!=-1;i=E[i].nex){ if(i==(id^1)) continue; int v=E[i].v; if(!dfn[v]){ tarjan(v,i); if(dfn[u]<low[v]) Ma[mp(u,v)]=Ma[mp(v,u)]=1; low[u]=min(low[u],low[v]); }else low[u]=min(low[u],dfn[v]); }return; } struct Union{ int f[MAXN]; void init(){for(int i=1;i<=N;i++) f[i]=i;return;} int find(int x){return f[x]==x?x:f[x]=find(f[x]);} void merge(int x,int y){int t1=find(x),t2=find(y);f[t2]=t1;return;} }S; int col[MAXN],MM[MAXN],dep[MAXN],fa[MAXN][21]; void dfs(int u,int fath){ dep[u]=dep[fath]+1; fa[u][0]=fath; for(int i=1;(1<<i)<=dep[u];i++) fa[u][i]=fa[fa[u][i-1]][i-1]; for(auto v:vec[u]) if(v!=fath) dfs(v,u); } int Qlca(int u,int v){ if(dep[u]<dep[v]) swap(u,v); for(int i=20;i>=0;i--) if(dep[u]-(1<<i)>=dep[v]) u=fa[u][i]; if(u==v) return u; for(int i=20;i>=0;i--) if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i]; return fa[u][0]; } int S1[MAXN],S2[MAXN]; void dfs1(int u,int fath){ for(auto v:vec[u]){ if(v==fath) continue; dfs1(v,u); S1[u]+=S1[v],S2[u]+=S2[v]; }return; } int main(){ //freopen("maker.in","r",stdin); N=read(),M=read(),Q=read(); memset(head,-1,sizeof(head)); S.init(); for(int i=1;i<=M;i++){U[i]=read(),V[i]=read();add(U[i],V[i]),add(V[i],U[i]);} for(int i=1;i<=N;i++) if(!dfn[i]) tarjan(i,-1); for(int i=1;i<=M;i++) if(!Ma[mp(U[i],V[i])]) S.merge(U[i],V[i]); for(int i=1;i<=N;i++){int x=S.find(i); if(!MM[x]) MM[x]=++MM[0]; col[i]=MM[x];} //for(int i=1;i<=N;i++) printf("%d ",col[i]);printf("\n"); for(int i=1;i<=M;i++) if(Ma[mp(U[i],V[i])]) if(!Ma1[mp(col[U[i]],col[V[i]])]){ int u=col[U[i]],v=col[V[i]]; Ma1[mp(u,v)]=Ma1[mp(v,u)]=1; vec[u].pb(v),vec[v].pb(u); } for(int i=1;i<=N;i++) if(!dep[i]) dfs(i,0); //for(int i=1;i<=N;i++) for(int j=1;j<=N;j++) printf("(%d,%d):%d\n",i,j,Qlca(i,j)); for(int i=1;i<=Q;i++){ int u=read(),v=read(); u=col[u],v=col[v]; if(u==v) continue; int LCA=Qlca(u,v); S1[u]++,S1[LCA]--; S2[v]++,S2[LCA]--; if(!LCA){printf("No\n");return 0;} } for(int i=1;i<=N;i++) if(dep[i]==1) dfs1(i,0); for(int i=2;i<=N;i++) if(S1[i]&&S2[i]){printf("No\n");return 0;} printf("Yes\n"); return 0; }/* 8 10 3 7 6 2 8 7 8 6 5 8 7 8 7 5 5 8 4 5 5 4 3 7 2 8 6 8 2 */
[x] CF566E
题意:给定每个点的信息,信息为距离一个点 $\leq 2$ 的所有点,我们不知道每个点对应哪个信息。求一棵满足该要求的树。 $n\leq 10^3$ 。
推性质的好题。
考虑两个非叶结点 $u,v$ ,若他们之间有边那么肯定能找到两个集合交集为 $2$ ,因为找 $u_{nex},v_{nex}$ 即可。
则我们已经找到所以非叶结点的连边,现在只要将叶子对应上去即可。
考虑叶子结点 $u$ 与其父亲结点 $v$ 之间的集合关系,可以发现若我们处理出所有非叶结点的所有距离 $\leq 1$ 的集合,则 $u$ 所除去所有叶结点就是 $v$ 。
那么我们只要知道每个叶结点对应的集合,可以发现对应 $u$ 的集合是存在 $u$ 的最小大小的那个。
而前面的必须保证非叶结点个数 $\geq 3$ ,而 $\leq 2$ 分别特判即可。
上述过程可以通过 $bitset$ 优化,时间复杂度 $O(\frac{n^3}{w})$ 。
#include<iostream> #include<cstring> #include<cstdio> #include<climits> #include<algorithm> #include<queue> #include<vector> #include<bitset> #define pii pair<int,int> #define mp make_pair #define pb push_back #define fi first #define se second using namespace std; inline int read(){ int f=1,ans=0; char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=1e3+11; bitset<MAXN> E[MAXN],G[MAXN],T[MAXN]; int N,vis[MAXN],Ma[MAXN][MAXN],P[MAXN]; bool ff=1; vector<pii> vec; int main(){ //freopen("2.in","r",stdin); N=read(); if(N==2){printf("1 2\n");return 0;} for(int i=1;i<=N;i++){ int siz=read(); ff&=(siz==N); for(int j=1;j<=siz;j++) E[i][read()]=1; } if(ff){for(int i=2;i<=N;i++) printf("1 %d\n",i);return 0;} for(int i=1;i<=N;i++){ for(int j=i+1;j<=N;j++){ if((E[i]&E[j]).count()==2){ int x=(E[i]&E[j])._Find_first(),y=(E[i]&E[j])._Find_next(x); if(!Ma[x][y]){Ma[x][y]=Ma[y][x]=1;vec.pb(mp(x,y));} vis[x]=1,vis[y]=1; } } } int cnt=0; for(int i=1;i<=N;i++) cnt+=vis[i]; if(cnt==2){ int ps0=-1,ps1=-1; for(int i=1;i<=N;i++) if(E[i].count()!=N){ if(ps0==-1) ps0=i; else if(E[i]!=E[ps0]){ps1=i;break;} } int X=vec[0].fi,Y=vec[0].se; printf("%d %d\n",X,Y); for(int i=1;i<=N;i++) if(E[ps0][i]&&i!=X&&i!=Y) printf("%d %d\n",X,i); for(int i=1;i<=N;i++) if(E[ps1][i]&&i!=X&&i!=Y) printf("%d %d\n",Y,i); return 0; } for(auto pp:vec) printf("%d %d\n",pp.fi,pp.se); for(int i=1;i<=N;i++){ if(vis[i]){ for(int j=1;j<=N;j++) if(Ma[i][j]) G[i][j]=1; G[i][i]=1;continue; } } for(int i=1;i<=N;i++){ int ps=0; if(vis[i]) continue; for(int j=1;j<=N;j++){ if(!E[j][i]) continue; if(!ps){ps=j;continue;} if(E[j].count()<E[ps].count()) ps=j; } P[i]=ps; } for(int i=1;i<=N;i++){ if(vis[i]) continue; int u=P[i]; for(int j=1;j<=N;j++) if(E[u][j]&&!vis[j]) E[u][j]=0; for(int j=1;j<=N;j++) if(vis[j]) if(E[u]==G[j]) printf("%d %d\n",i,j); }return 0; }
[] CF571D
题意:维护两个下标集合 $\{a\},\{b\}$,支持合并,使 $a$ 中 $x$ 所在的集合所有元素加 $x$,或使 $b$ 中 $x$ 所在的集合全赋值为 $x$,单点查询。$n,q\leq 5\times 10^5$ 。
考虑对 $a,b$ 分别建重构树,则问题转换为树上结点所包含的叶子区间加,区间赋值。但是两树的 $dfs$ 序是不同的,则可以先查找被 $b$ 赋到了多少在查询 $a$ 树的情况。
对于 $b$ 树可以直接拿线段树维护,而 $a$ 树由于有时间限制相当于二维数点直接维护即可。
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<climits> #include<set> #include<vector> #define LL long long #define pii pair<int,int> #define fi first #define se second #define pb push_back #define mp make_pair using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=1e6+11; int N,Q,tot,rev[MAXN],siz[MAXN],dfn[MAXN],vis[MAXN]; vector<int> vecA[MAXN],vecB[MAXN]; LL Ans[MAXN]; struct Segment{ int tag[MAXN<<2]; void pushdown(int k){ if(tag[k]==-1) return; tag[k<<1]=tag[k],tag[k<<1|1]=tag[k]; tag[k]=-1;return; } void Cover(int k,int l,int r,int x,int y,int w){ if(x<=l&&r<=y){tag[k]=w;return;} pushdown(k); int mid=(l+r)>>1; if(x<=mid) Cover(k<<1,l,mid,x,y,w); if(mid<y) Cover(k<<1|1,mid+1,r,x,y,w); return; } int Query(int k,int l,int r,int ps){ if(l==r) return tag[k]; pushdown(k); int mid=(l+r)>>1; if(ps<=mid) return Query(k<<1,l,mid,ps); if(mid<ps) return Query(k<<1|1,mid+1,r,ps); } }SB; struct BIT{ LL Ans[MAXN]; int lowbit(int x){return x&-x;} void Modify(int x,int w){for(;x<=N;x+=lowbit(x)) Ans[x]+=w;return;} void Add(int l,int r,int w){if(l>r) return;Modify(l,w),Modify(r+1,-w);return;} LL Query(int x){LL res=0;for(;x;x-=lowbit(x)) res+=Ans[x];return res;} }SA; struct Union{ int f[MAXN<<1],siz[MAXN]; void init(){for(int i=1;i<=(N<<1);i++) f[i]=i,siz[i]=1;return;} int find(int xx){return f[xx]==xx?xx:f[xx]=find(f[xx]);} void merge(int x,int y){ int t1=find(x),t2=find(y); f[t2]=t1;siz[t1]+=siz[t2];return; } }UA,UB,U; struct Query{ int opt,x,y,rt; }Que[MAXN]; void dfs(int u){ vis[u]=1;if(u<=N){dfn[u]=++dfn[0],rev[dfn[0]]=u;siz[u]=1;return;} dfn[u]=dfn[0]+1; for(auto v:vecB[u]) dfs(v),siz[u]+=siz[v]; return; } int dfn1[MAXN],rev1[MAXN],siz1[MAXN]; void dfs1(int u){ vis[u]=1;if(u<=N){dfn1[u]=++dfn1[0],rev1[dfn1[0]]=u;siz1[u]=1;return;} dfn1[u]=dfn1[0]+1; for(auto v:vecA[u]) dfs1(v),siz1[u]+=siz1[v]; } struct Real{ int L,R,W,tim; int opt,id; }Quee[MAXN]; bool cmp(Real x1,Real x2){ if(x1.tim==x2.tim) return abs(x1.opt)<abs(x2.opt); return x1.tim<x2.tim; }char str[4]; signed main(){ //freopen("A.in","r",stdin); N=read(),Q=read(); UA.init(),UB.init();U.init(); for(int i=1;i<=Q;i++){ scanf("%s",str+1);int opt; if(str[1]=='U') opt=1; if(str[1]=='M') opt=2; if(str[1]=='A') opt=3; if(str[1]=='Z') opt=4; if(str[1]=='Q') opt=5; Que[i].opt=opt; if(opt<=2) Que[i].x=read(),Que[i].y=read(); if(opt==1) U.merge(Que[i].x,Que[i].y); else if(opt>=3){ Que[i].x=read(); if(opt==4) Que[i].y=0; if(opt==3){ Que[i].y=U.siz[U.find(Que[i].x)]; } } } int cntA=N,cntB=N; for(int i=1;i<=Q;i++){ if(Que[i].opt==1){ int u=Que[i].x,v=Que[i].y,tu=UA.find(u),tv=UA.find(v); cntA++; vecA[cntA].pb(tu),vecA[cntA].pb(tv);UA.f[tu]=cntA,UA.f[tv]=cntA; } if(Que[i].opt==2){ int u=Que[i].x,v=Que[i].y,tu=UB.find(u),tv=UB.find(v); cntB++; vecB[cntB].pb(tu),vecB[cntB].pb(tv);UB.f[tu]=cntB,UB.f[tv]=cntB; } if(Que[i].opt==3) Que[i].rt=UA.find(Que[i].x); if(Que[i].opt==4) Que[i].rt=UB.find(Que[i].x); } memset(vis,0,sizeof(vis)); for(int i=cntB;i>=1;i--) if(!vis[i]) dfs(i); memset(vis,0,sizeof(vis)); for(int i=cntA;i>=1;i--) if(!vis[i]) dfs1(i); //cerr<<dfn1[2]<<endl;return 0; for(int i=1;i<=Q;i++){ if(Que[i].opt<=2) continue; if(Que[i].opt==3){ ++tot; int rt=Que[i].rt; Quee[tot].L=dfn1[rt],Quee[tot].R=dfn1[rt]+siz1[rt]-1; Quee[tot].W=Que[i].y;Quee[tot].tim=i; continue; } if(Que[i].opt==4){ int rt=Que[i].rt; SB.Cover(1,1,N,dfn[rt],dfn[rt]+siz[rt]-1,i); continue; } int u=Que[i].x,p=SB.Query(1,1,N,dfn[u]); int L=p+1,R=i-1; Ans[i]=Que[p].y; ++tot;Quee[tot].opt=-1,Quee[tot].id=i,Quee[tot].tim=L-1; ++tot;Quee[tot].opt= 1;Quee[tot].id=i,Quee[tot].tim=R; } sort(Quee+1,Quee+tot+1,cmp);int ps=1; for(int i=0;i<=Q;i++){ while(ps<=tot&&Quee[ps].tim==i){ if(!Quee[ps].opt){SA.Add(Quee[ps].L,Quee[ps].R,Quee[ps].W);ps++;continue;} LL id=Quee[ps].id,opt=Quee[ps].opt,W=SA.Query(dfn1[Que[id].x]); //cerr<<"W:"<<W<<" "<<dfn1[Que[id].x]<<endl; Ans[id]+=opt*W; ps++; } } for(int i=1;i<=Q;i++) if(Que[i].opt==5) printf("%lld\n",Ans[i]); return 0; }/* 3 5 2 1 1 3 3 1 3 3 3 4 1 4 5 2 */
[] CF576D
题意:有一张 $n$ 个点 $m$ 的有向图,其中变为 $u,v,d$ ,表示在经过 $d$ 条边后 $u,v$ 才出现,求 $1$ 到 $n$ 的最短路。$n,m\leq 150$ 。
感觉很矩乘,考虑我们维护到 $T$ 时刻 $u$ 是否能到 $v$ 的矩阵。
则我们只要在每个 $d$ 进行一遍洪水填充即可,而此过程可以快速幂 $bitset$ 优化,时间复杂度 $O(\dfrac{n^4}{w})$。
#include<iostream> #include<cstring> #include<cstdio> #include<climits> #include<algorithm> #include<queue> #include<vector> #include<bitset> #define pii pair<int,int> #define mp make_pair #define pb push_back #define fi first #define se second using namespace std; inline int read(){ int f=1,ans=0; char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=151; int N,M,dis[MAXN],Minn=INT_MAX; vector<int> vec[MAXN]; queue<int> que; struct Matrix{bitset<MAXN> A[MAXN],B[MAXN];void init(){for(int i=1;i<=N;i++) A[i].reset(),B[i].reset();}}F,G; struct Edge{int u,v,w;}E[MAXN]; bool cmp(Edge x1,Edge x2){return x1.w<x2.w;} int tmp[MAXN][MAXN]; Matrix operator*(Matrix x1,Matrix x2){ Matrix x3;x3.init(); for(int i=1;i<=N;i++) for(int j=1;j<=N;j++) tmp[i][j]=(x1.A[i]&x2.B[j]).any(); for(int i=1;i<=N;i++) for(int j=1;j<=N;j++) x3.A[i][j]=tmp[i][j],x3.B[j][i]=tmp[i][j]; return x3; } Matrix ksm(Matrix a,int b){ Matrix Ans; Ans.init(); for(int i=1;i<=N;i++) Ans.A[i][i]=1,Ans.B[i][i]=1; while(b){if(b&1) Ans=Ans*a;a=a*a,b>>=1;} return Ans; } void calc(Matrix Q,int ti){ while(!que.empty()) que.pop();memset(dis,127,sizeof(dis)); for(int i=1;i<=N;i++) if(Q.A[1][i]) dis[i]=ti,que.push(i); while(!que.empty()){ int xx=que.front(); que.pop(); for(auto v:vec[xx]) if(dis[v]>dis[xx]+1){dis[v]=dis[xx]+1;que.push(v);} }Minn=min(Minn,dis[N]); return; } int main(){ //freopen("A.in","r",stdin); N=read(),M=read(); for(int i=1;i<=M;i++) E[i].u=read(),E[i].v=read(),E[i].w=read(); sort(E+1,E+M+1,cmp); F.init(),G.init(); for(int i=1;i<=N;i++) F.A[i][i]=1,F.B[i][i]=1; int ti=0; for(int i=1;i<=M;i++){ Matrix S=ksm(G,E[i].w-E[i-1].w); F=F*S; /*print(S);*/calc(F,E[i].w); vec[E[i].u].pb(E[i].v); G.A[E[i].u][E[i].v]=1; G.B[E[i].v][E[i].u]=1; } calc(F,E[M].w); if(Minn==2139062143){printf("Impossible\n");return 0;} printf("%d\n",Minn); return 0; }/* 3 2 1 2 0 2 3 1 */
[x] CF585E
题意:给定序列 $A$,求有多少个 $x,S$ 满足 $x \notin S$,$\gcd\{S\} \neq 1$,$\gcd(x, \gcd\{S\}) = 1$ 。
我们设 $F_i$ 表示与 $i$ 互质的点的个数,$G_i$ 表示 $gcd$ 为 $i$ 的集合个数,$c_i$ 表示原序列中有多少个数是 $i$。
则
$$
F_x=\sum_i[\gcd(i,x)==1]\cdot c_i\\=\sum_i c_i\sum_{d|gcd(i,x)} \mu(d)\\=\sum_{d|x} \mu(d)\sum_{d|i} c_i
$$
设 $T_d=\sum_{d|i} c_i$ ,则 $F_x=\sum_{d|x} T_d$ 。
而对于 $T,F$ ,我们都可以通过狄利克雷前缀和维护。
我们设 $g_x$ 表示 $gcd$ 为 $x$ 的倍数的集和个数,则 $g_x=2^{T_x}-1$ 。
而
$$
G_x=\sum_{x|d} g_d
$$
可以在通过狄利克雷前缀和维护。时间复杂度 $O(M\log \log M)$ ,$M$ 表示值域。
#include<iostream> #include<cstring> #include<cstdio> #include<climits> #include<algorithm> #include<queue> #include<vector> #define LL long long #define mod 1000000007 #define pii pair<int,int> #define mp make_pair #define pb push_back #define fi first #define se second using namespace std; inline int read(){ int f=1,ans=0; char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=1e7+11; const int maxn=1e7; const int MAXM=5e5+11; int mu[MAXN],v[MAXN],pri[664581],N,c[MAXN],pw[MAXM],S[MAXN],F[MAXN],Ans; void init(){ mu[1]=1; for(int i=2;i<MAXN;i++){ if(!v[i]){v[i]=i,mu[i]=-1;pri[++pri[0]]=i;} for(int j=1;j<=pri[0]&&pri[j]<=MAXN/i;j++){ v[i*pri[j]]=pri[j]; if(!(i%pri[j])) break; mu[pri[j]*i]=-mu[i]; } }return; } int main(){ //freopen("1.in","r",stdin); N=read(); for(int i=1;i<=N;i++) c[read()]++; init(); pw[0]=1; for(int i=1;i<=N;i++) pw[i]=(LL)pw[i-1]*2ll%mod; for(int i=1;i<=maxn;i++) S[i]=c[i]; for(int i=1;i<=pri[0];i++) for(int j=maxn/pri[i];j>=1;j--) S[j]=(LL)(S[j]+S[j*pri[i]])%mod; for(int i=1;i<=maxn;i++) F[i]=mu[i]*S[i],F[i]=(F[i]%mod+mod)%mod; for(int i=1;i<=maxn;i++) S[i]=pw[S[i]]-1; for(int i=1;i<=pri[0];i++) for(int j=1;j<=maxn/pri[i];j++) S[j]-=S[j*pri[i]],S[j]=(S[j]%mod+mod)%mod; for(int i=1;i<=pri[0];i++) for(int j=1;j<=maxn/pri[i];j++) F[pri[i]*j]=(LL)(F[pri[i]*j]+F[j])%mod; for(int i=2;i<=maxn;i++) Ans=(LL)(Ans+(LL)F[i]*S[i]%mod)%mod; printf("%d\n",Ans); return 0; }
[x] CF603E
题意:期初有 $n$ 个点,每次询问加入一条边后是否存在一个边集使得每个点度数都为奇数,且使得边权的最小值最大。$n\leq 10^5,q\leq 5\times 10^5$ 。
可以发现一个图可以选出一个点集使得度数都为奇的充要条件是每个联通块都为偶数。
证明可以考虑我们将度数想成异或 $1$ ,那么问题变成从全 $0$ 转换成全 $1$ 。则我们每次可以选择联通块中任意两个异或 $1$ 。
则对于偶数联通快很容易做到这一点,而对于奇数联通快无法做到。 因为最后会有 $n$ 个 $1$ ,而我们每次会让 $1$ 的个数 $+2,-2,+0$ 无法变成奇数,故得证。
我们发现答案是非严格单调不升,可以选择 $lct$ 或整体二分来实现。
考虑如何整体二分,设 $solve(l,r,x,y)$ 表示只考虑询问在 $[l,r]$ 中,且最后答案在 $[x,y]$ 中的情况,利用可撤销并查集即可维护。
#include<iostream> #include<cstring> #include<cstdio> #include<climits> #include<algorithm> #include<queue> #include<vector> #include<stack> #define pii pair<int,int> #define mp make_pair #define pb push_back #define fi first #define se second using namespace std; inline int read(){ int f=1,ans=0; char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=3e5+11; int N,M,Ans[MAXN],tmp[MAXN],W,Sum,id[MAXN]; struct node{int u,v,w,id;}E[MAXN],e[MAXN]; stack<pii> sta; struct Union{ int siz[MAXN],f[MAXN],Res[MAXN]; void init(){for(int i=1;i<=N;i++) siz[i]=1,f[i]=i,Res[i]=1;return;} int find(int x){return f[x]==x?x:find(f[x]);} void merge(int x,int y){ int t1=find(x),t2=find(y); if(t1==t2) return; if(siz[t1]<siz[t2]) swap(t1,t2); Sum-=(siz[t1]&1),Sum-=(siz[t2]&1); siz[t1]+=siz[t2];f[t2]=t1; Sum+=(siz[t1]&1); sta.push(mp(t1,t2));return; } void reset(){ int t1=sta.top().fi,t2=sta.top().se; sta.pop(); Sum-=(siz[t1]&1); siz[t1]-=siz[t2];f[t2]=t2; Sum+=(siz[t1]&1),Sum+=(siz[t2]&1); return; } }U; bool cmp(node x1,node x2){return x1.w<x2.w;} vector<int> vec[MAXN]; void solve(int l,int r,int x,int y){ //cerr<<"l:"<<l<<" r:"<<r<<" x:"<<x<<" y:"<<y<<endl; if(l>r) return; int mid=(l+r)>>1,siz=sta.size(); for(int i=l;i<=mid;i++) if(id[i]<x) U.merge(E[i].u,E[i].v); int ans=-1;for(int i=x;i<=y;i++){ if(e[i].id<=mid) U.merge(e[i].u,e[i].v); if(!Sum){ans=i;break;} } //cerr<<mid<<" "<<ans<<endl;//exit(0); if(ans==-1){ while(sta.size()!=siz) U.reset(); siz=sta.size(); for(int i=l;i<=mid;i++) if(id[i]<x) U.merge(E[i].u,E[i].v); solve(mid+1,r,x,y); while(sta.size()!=siz) U.reset(); return; }Ans[mid]=e[ans].w; while(sta.size()!=siz) U.reset(); siz=sta.size(); for(int i=l;i<=mid;i++) if(id[i]<x) U.merge(E[i].u,E[i].v); solve(mid+1,r,x,ans); while(sta.size()!=siz) U.reset(); siz=sta.size(); for(int i=x;i<ans;i++) if(e[i].id<l) U.merge(e[i].u,e[i].v); solve(l,mid-1,ans,y); while(sta.size()!=siz) U.reset(); return; } int main(){ //freopen("B.in","r",stdin); N=read(),M=read(); U.init(); Sum=N; for(int i=1;i<=M;i++) E[i].u=read(),E[i].v=read(),E[i].id=i,tmp[i]=E[i].w=read(),e[i]=E[i]; sort(e+1,e+M+1,cmp); for(int i=1;i<=M;i++) id[e[i].id]=i; solve(1,M,1,M); for(int i=1;i<=M;i++) if(!Ans[i]) Ans[i]=-1; for(int i=1;i<=M;i++) printf("%d\n",Ans[i]); return 0; }/* 4 4 1 3 4 2 4 8 1 2 2 3 4 3 */
[x] CF605E
题意:定义 $p_{i,j}$ 为每天 $i$ 到 $j$ 的边出现的概率,求最优策略下 $1$ 到 $n$ 的期望天数。 $n\leq 500$ 。
我们设 $E_u$ 表示 $u$ 到 $n$ 的期望最短时间。假设我们知道了所有点的 $E$ 值,则我们发现一个点 $u$ 可以走到点 $v$ 的条件是 $E_u>E_v$ ,则转移图是个 $DAG$ 。
我们如何去维护上述过程,显然 $u$ 会走一个期望最小的。 那么我们设 $f_u$ 表示在 $u$ 号结点且 $E_u>E_v$ 的所有转移代价。
由于时间的独立性可得 $$f_u=\sum E_v\times p_{u,v}\prod_x (1-p_{u,x})$$ $x$ 代表 $E$ 值在 $v$ 前面的数。
而对于 $E_u$ 因为存在时刻1无边可走则 $E_u=P(E_u+1)+f_u$ ,$P$ 值啥边都没有的概率。解得 $E_u=\dfrac{f_u+P}{1-P}$ ,暴力维护即可。
#include<iostream> #include<cstring> #include<cstdio> #include<cstring> #include<vector> #include<queue> #include<algorithm> #include<climits> #define pii pair<int,int> #define pb push_back #define mp make_pair #define fi first #define se second using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=1e3+11; int N,vis[MAXN]; double p[MAXN][MAXN]; double f[MAXN],now[MAXN]; double calc(int u){return (f[u]+now[u])/(1-now[u]);} void update(int u){ vis[u]=1; for(int i=1;i<=N;i++) if(!vis[i]) f[i]+=(calc(u)+1)*p[i][u]*now[i],now[i]*=(1-p[i][u]); return; } int main(){ N=read(); for(int i=1;i<=N;i++) for(int j=1;j<=N;j++){int x=read();p[i][j]=(double)x/100;} if(N==1){printf("0\n");return 0;} for(int i=1;i<N;i++) now[i]=1;update(N); while(1){ int ps=0; for(int i=1;i<=N;i++) if(!vis[i]) if(!ps||calc(i)<calc(ps)) ps=i; //cerr<<"ps:"<<ps<<" calc:"<<calc(ps)<<endl; update(ps); if(ps==1){printf("%.10lf\n",calc(1));return 0;} } }