• 2022 暑期复健训练(持续更新)


    7.6

    NWERC 2016

    A. Arranging Hat

    \(f[i][j]\) 表示前 \(i\) 个串,总共修改了 \(j\) 次,得到的最小值是多少。
    转移就枚举一个 \(k\),实现一个 \(work(a_{i+1},f_{i,j},k)\) 表示将 \(a_{i+1}\) 经过不超过 \(k\) 次修改,得到的大于等于 \(f_{i,j}\) 的最小字典序
    这部分是一个情况稍多的贪心,想清楚细节。
    还有一个问题是,前 \(4\) 位可以确定 \(10^4\) 个串的大小关系,所以 DP 数组的第二维只需要开到 \(4n\) 即可。

    #include <bits/stdc++.h>
    using namespace std;
    const int N=45,M=405;
    #define ll long long
    int n,m;
    struct Node{
    	char s[M];
    	bool gg;
    	void print(){
    		for (int i=1;i<=m;i++) putchar(s[i]);
    		puts("");
    	}
    };
    Node a[N],dp[N][205];
    int lst[N][205];
    bool check(Node a,Node b){
    	for (int i=1;i<=m;i++){
    		if (a.s[i]==b.s[i]) continue;
    		return a.s[i]<b.s[i];
    	}
    	return true;
    }
    Node work(Node lst,Node now,int kk){
    	if (kk==0){
    		if (check(lst,now)) {
    			now.gg=false;
    			return now;
    		}
    		now.gg=true;
    		return now;
    	}
    	bool flag=false;int k=kk;
    	Node tmp=now;int high=1;
    	for (int i=1;i<=m&&k;i++)
    		if (tmp.s[i]!=lst.s[i]){
    			tmp.s[i]=lst.s[i];
    			k--;high=i;
    		}
    	if (check(lst,tmp)) {
    		tmp.gg=false;
    		return tmp;
    	}
    	bool all_9=true;
    	for (int i=1;i<=high;i++)
    		if (tmp.s[i]!='9') {
    			all_9=false;
    			break;
    		}
    	if (all_9){
    		tmp.gg=true;
    		return tmp;
    	}
    	k=kk;tmp=now;
    	static bool vis[M];
    	for (int i=1;i<=m;i++) vis[i]=false;
    	for (int i=1;i<=m&&k;i++)
    		if (tmp.s[i]!=lst.s[i]){
    			tmp.s[i]=lst.s[i];
    			k--;vis[i]=true;
    		}
    	for (int i=high;i>=1;i--){
    		if (tmp.s[i]=='9') continue;
    		k=-1;
    		for (int j=i;j<=n;j++) if (vis[j]) k++;
    		tmp.s[i]=lst.s[i]+1;
    		if (tmp.s[i]==now.s[i]) k++;
    		for (int j=i+1;j<=n;j++){
    			if (k>0){
    				tmp.s[j]='0';
    				if (now.s[j]!='0') k--;
    			}else tmp.s[j]=now.s[j];
    		}
    		break;
    	}
    	tmp.gg=false;
    	return tmp;
    }
    int ans[N];
    int main(){
    //	freopen ("a.in","r",stdin);
    	scanf ("%d%d",&n,&m);
    	for (int i=1;i<=n;i++) scanf ("%s",a[i].s+1);
    	for (int i=1;i<=m;i++) a[0].s[i]='0';
    	for (int i=0;i<=n;i++)
    		for (int j=0;j<=n*4;j++)
    			dp[i][j].gg=true;
    	dp[0][0]=a[0];
    	for (int i=0;i<n;i++){
    		for (int j=0;j<=min(i*m,n*4);j++)
    			if (!dp[i][j].gg){
    				for (int k=0;k<=m;k++){
    					if (j+k>n*4) break;
    					Node tmp=work(dp[i][j],a[i+1],k);
    					if (tmp.gg==true) continue;
    					if (dp[i+1][j+k].gg==true) dp[i+1][j+k]=tmp,lst[i+1][j+k]=j;
    					else if (check(tmp,dp[i+1][j+k])){
    						dp[i+1][j+k]=tmp;
    						lst[i+1][j+k]=j;
    					}
    				}
    			}
    		}
    	int pos=-1;
    	for (int i=0;i<=n*4;i++)
    		if (!dp[n][i].gg) {
    			pos=i;
    			break;
    		}
    	for (int i=n;i>=1;i--) ans[i]=pos,pos=lst[i][pos];
    	for (int i=1;i<=n;i++) dp[i][ans[i]].print();
    	return 0;
    }
    
    

    B. British Menu

    由于强连通分量大小不超过 5,把它们缩起来,然后暴力求出同一个分量中两两之间的距离
    在缩点后建立的新图上跑拓扑序 dp 求最长路,\(dis[x][i]\) 表示停留在原图第 \(i\) 个点上的最长路长度,转移即可。
    细节较多

    #include <bits/stdc++.h>
    using namespace std;
    const int N=1e5+5,E=1e6+5;
    int Head[N],Next[E],Adj[E],Id1[E],Id2[E],tot=0;
    void addedge(int u,int v,int id1=0,int id2=0){
    	Next[++tot]=Head[u],Head[u]=tot,Adj[tot]=v;
    	Id1[tot]=id1,Id2[tot]=id2;
    }
    int dfn[N],low[N],Time=0,s[N],top=0;
    bool vis[N];int bel[N],cnt=0;
    int to[N];
    struct Node{
    	vector <int> p;
    	bool e[5][5];
    	int dis[5][5];
    	void dfs(int f,int x,int l){
    		vis[x]=true;dis[f][x]=max(dis[f][x],l);
    		for (int i=0;i<p.size();i++)
    			if (e[x][i]&&!vis[i])
    				dfs(f,i,l+1);
    		vis[x]=false;
    	}
    	void calc_dist(){
    		if (p.size()==1) return;
    		for (int i=0;i<p.size();i++) dfs(i,i,0);
    	}
    }w[N];
    void Tarjan(int x){
    	dfn[x]=low[x]=++Time;
    	s[++top]=x;vis[x]=true;
    	for (int e=Head[x];e;e=Next[e])
    		if (!dfn[Adj[e]]) Tarjan(Adj[e]),low[x]=min(low[x],low[Adj[e]]);
    		else if (vis[Adj[e]]) low[x]=min(low[x],dfn[Adj[e]]);
    	if (dfn[x]==low[x]){
    		cnt++;
    		while (s[top+1]!=x){
    			bel[s[top]]=cnt;
    			to[s[top]]=w[cnt].p.size();
    			w[cnt].p.emplace_back(x);
    			vis[s[top--]]=false;
    		}
    	}
    }
    int u[E],v[E],in[N];
    int dis[N][5];
    int main(){
    	int n,m;scanf ("%d%d",&n,&m);
    	for (int i=1;i<=m;i++) {
    		scanf ("%d%d",&u[i],&v[i]);
    		addedge(u[i],v[i]);
    	}
    	for (int i=1;i<=n;i++) if (!dfn[i]) Tarjan(i);
    	memset (Head,0,sizeof(Head));
    	memset (Next,0,sizeof(Next));
    	memset (Adj,0,sizeof(Adj));tot=0;
    	memset (vis,0,sizeof(vis));
    	for (int i=1;i<=m;i++)
    		if (bel[u[i]]==bel[v[i]])
    			w[bel[u[i]]].e[to[u[i]]][to[v[i]]]=true;
    	for (int i=1;i<=cnt;i++)
    		w[i].calc_dist();
    	for (int i=1;i<=m;i++)
    		if (bel[u[i]]!=bel[v[i]])
    			addedge(bel[u[i]],bel[v[i]],to[u[i]],to[v[i]]),in[bel[v[i]]]++;
    	queue <int> Q;
    	for (int i=1;i<=cnt;i++) if (!in[i]) Q.push(i);
    	int ans=0;
    	while (!Q.empty()){
    		int x=Q.front();Q.pop();
    		for (int e=Head[x];e;e=Next[e]) {
    			in[Adj[e]]--;int mx=0;
    			if (in[Adj[e]]==0) Q.push(Adj[e]);
    			for (int i=0;i<w[x].p.size();i++) mx=max(mx,w[x].dis[i][Id1[e]]+dis[x][i]);
    			dis[Adj[e]][Id2[e]]=max(dis[Adj[e]][Id2[e]],mx+1);
    		}
    		for (int i=0;i<w[x].p.size();i++) ans=max(ans,dis[x][i]+(int)w[x].p.size()-1);
    	}
    	printf ("%d",ans+1);
    	return 0;
    }
    
    

    J Jupiter Orbiter

    拆点最大流判定,很显然的建图,没什么好说的

    #include <bits/stdc++.h>
    using namespace std;
    const int N=10005,E=200005,INF=1e9;
    int Head[N],Next[E],Adj[E],Flow[E],tot=1;
    inline void addedge(int u,int v,int w){
        Next[++tot]=Head[u],Head[u]=tot,Adj[tot]=v,Flow[tot]=w;
        Next[++tot]=Head[v],Head[v]=tot,Adj[tot]=u,Flow[tot]=0;
    }
    int S,T,level[N];
    int Q[N];
    bool bfs(){
        memset (level,-1,sizeof(level));
    	int l=1,r=0;
    	Q[++r]=S,level[S]=0;
        while (l<=r){
            int x=Q[l++];
            for (int e=Head[x];e;e=Next[e])
                if (level[Adj[e]]==-1&&Flow[e])
                    level[Adj[e]]=level[x]+1,Q[++r]=Adj[e];
        }
        return level[T]!=-1;
    }
    int dfs(int x,int flow){
        if (x==T||!flow) return flow;
        int ret=0,c;
        for (int e=Head[x];e;e=Next[e])
            if (level[Adj[e]]==level[x]+1&&Flow[e]&&(c=dfs(Adj[e],min(flow-ret,Flow[e])))){
                ret+=c;Flow[e]-=c,Flow[e^1]+=c;
                if (ret==flow) break;
            }
        if (!ret) level[x]=-1;
        return ret;
    }
    int to[N],c[N];
    int n,q,s;
    int ID(int i,int j){
    	return n+2+i*q+j;
    }
    int ID2(int i,int j){
    	return n+2+q*n+i*q+j;
    }
    int main(){
    	scanf ("%d%d%d",&n,&q,&s);
    	for (int i=1;i<=s;i++) scanf ("%d",&to[i]);
    	for (int i=1;i<=q;i++) scanf ("%d",&c[i]);
    	int id=0;S=++id,T=++id;
    	for (int i=1;i<=n;i++){
    		int d;scanf ("%d",&d);
    		++id;addedge(id,T,d);
    		for (int j=1,a;j<=s;j++){
    			scanf ("%d",&a);
    			addedge(S,ID(i,to[j]),a);
    		}
    		for (int j=1;j<=q;j++) {
    			addedge(ID(i,j),ID2(i,j),c[j]);
    			addedge(ID2(i,j),id,INF);
    		}
    		if (i!=1){
    			for (int j=1;j<=q;j++)
    				addedge(ID2(i-1,j),ID(i,j),c[j]);
    		}
    	}
    	while (bfs()) dfs(S,INF);
    	bool flag=true;
    	for (int e=Head[S];e;e=Next[e])
    		if (Flow[e]) flag=false;
    	puts(flag?"possible":"impossible");
    	return 0;
    }
    
    

    7.5

    NWERC 2018

    A. Access Points

    很容易发现横纵坐标独立,可以做两个一维的,答案加起来。
    结论:如果一段 \(x_i\dots x_j\) 是确定的,那么最小化 \(\sum_{k=j}^i(a-x_k)^2\) 时,\(a=\frac{1}{j-i+1}\sum_{k=j}^ix_k\)
    现在,我们假设原序列被分成了若干段相同的,那么当前段如果不符合递增的要求,就不断向前合并,使用单调栈维护这个过程

    #include <bits/stdc++.h>
    using namespace std;
    #define ld long double
    const int N=100005;
    ld x[N],y[N];int n;
    ld ans=0;
    struct Node{
    	int l,r;ld val;
    }s[N];
    Node merge(Node p,Node q){
    	return (Node){min(p.l,q.l),max(p.r,q.r),(p.val*(p.r-p.l+1)+q.val*(q.r-q.l+1))/(p.r-p.l+1+q.r-q.l+1)};
    }
    void solve(ld*a){
    	int top=0;
    	for (int i=1;i<=n;i++){
    		Node now=(Node){i,i,a[i]};
    		while (top&&now.val<s[top].val){
    			now=merge(now,s[top]);
    			--top;
    		}
    		s[++top]=now;
    	}
    	static ld b[N];
    	for (int i=1;i<=top;i++)
    		for (int j=s[i].l;j<=s[i].r;j++)
    			b[j]=s[i].val;
    	for (int i=1;i<=n;i++) ans+=(a[i]-b[i])*(a[i]-b[i]);
    }
    int main(){
    	scanf ("%d",&n);
    	for (int i=1;i<=n;i++) scanf ("%Lf%Lf",&x[i],&y[i]);
    	solve(x);
    	solve(y);
    	printf ("%.8Lf",ans);
    	return 0;
    }
    

    F. Fastest Speedrun

    好题。
    先把所有的 \(a[i][j]\) 减去 \(s[i]\),然后把 \(s[i]\) 累加到答案去,使秘密路径变成免费的。
    之后按照 \(x[i]\to i\) 连边,形成了一个基环树森林。
    我们可以发现,如果打掉一个环上某一个点,那么可以顺序用 \(0\) 的代价打掉整个连通块,而又可以观察发现最优解一定是通过某种方式快速拿到 \(n\) 武器,再打掉剩下的。
    所以我们把环上所有点的 \(a[i][j]\) 减去这个环最小的 \(a[i][n]\) ,跑 \(0\to n\) 的最短路即可。一个点在最短路上,说明它是在获得 \(n\) 的过程中需要打掉的,否则表示它是回头再来打的。
    时间复杂度 \(O(n^2)\)

    #include <bits/stdc++.h>
    using namespace std;
    #define ll long long
    const int N=2505;
    int x[N],s[N],a[N][N];
    vector <int> vec[N];
    int n,m=0;
    bool vis[N];
    void dfs(int x){
    	vis[x]=true;
    	for (int i=0;i<vec[x].size();i++)
    		if (!vis[vec[x][i]]) dfs(vec[x][i]);
    }
    ll dis[N];
    int e[N][N];
    int main(){
    	long long S=0;scanf ("%d",&n);
    	for (int i=1;i<=n;i++){
    		scanf ("%d%d",&x[i],&s[i]);
    		for (int j=0;j<=n;j++) scanf ("%d",&a[i][j]),a[i][j]-=s[i];
    		S+=s[i];vec[x[i]].emplace_back(i);
    	}
    	dfs(0);int cnt=0;
    	for (int i=1;i<=n;i++){
    		if (vis[i]) continue;
    		int t=i;bool flag=false;
    		for (int j=1;j<=n;j++) {
    			t=x[t];
    			if (t==i) {
    				flag=true;
    				break;
    			}
    		}
    		if (!flag) continue;
    		vis[i]=true;
    		t=x[i];m=a[i][n];
    		while (t!=i){
    			vis[t]=true;
    			m=min(m,a[t][n]);
    			t=x[t];
    		}
    		t=x[i];
    		for (int j=0;j<=n;j++) a[i][j]-=m;
    		while (t!=i){
    			for (int j=0;j<=n;j++) a[t][j]-=m;
    			t=x[t];
    		}
    		S+=m;
    	}
    	memset (e,0x3f,sizeof(e));
    	for (int i=0;i<=n;i++)
    		for (int j=1;j<=n;j++)
    			e[i][j]=a[j][i];
    	for (int i=1;i<=n;i++) e[x[i]][i]=0;
    	memset (dis,0x3f,sizeof(dis));
    	memset (vis,false,sizeof(vis));
    	dis[0]=0;
    	for (int t=1;t<=n;t++){
    		int mn=-1;
    		for (int i=0;i<=n;i++)
    			if (!vis[i]&&(mn==-1||dis[i]<dis[mn])) mn=i;
    		vis[mn]=true;
    		for (int i=0;i<=n;i++)
    			dis[i]=min(dis[i],dis[mn]+e[mn][i]);
    	}
    	printf ("%lld",dis[n]+S);
    	return 0;
    }
    

    7.4

    看了下牛客挑战赛 61 的题

    B “经典”问题

    纯随机数据,期望 mex 很小,暴力即可。

    C 维护序列

    差分,每个位置矩阵快速幂

    D 平衡

    考虑每次找到最高点,bfs 更新是一个不会形成环的过程,二分答案 check
    但是这样是 \(\log^2 n\) 的,发现权值很小,可以用桶记录最高点的高度,省去堆的复杂度。

    #include <bits/stdc++.h>
    using namespace std;
    const int N=500005;
    vector <int> a[N],b[N];
    int n,m,k,mx;
    vector < pair<int,int> > w[N];
    const int dx[]={0,1,0,-1,1,-1},dy[]={1,0,-1,0,-1,1};
    bool check(int lim){
    	for (int i=0;i<=1000;i++) w[i].clear();
    	for (int i=1;i<=n;i++)
    		for (int j=1;j<=m;j++)
    			b[i][j]=a[i][j];
    	for (int i=1;i<=n;i++)
    		for (int j=1;j<=m;j++)
    			if (b[i][j]>=0) w[b[i][j]].emplace_back(make_pair(i,j));
    	int tot=0;
    	for (int i=mx;i>=0;i--){
    		for (int j=0;j<w[i].size();j++){
    			int x=w[i][j].first,y=w[i][j].second;
    			if (b[x][y]==-2) continue;
    			for (int k=0;k<6;k++){
    				int xx=x+dx[k],yy=y+dy[k];
    				if (xx<1||xx>n||yy<1||yy>m) continue;
    				if (b[xx][yy]<0) continue;
    				if (b[xx][yy]<b[x][y]-lim){
    					tot+=b[x][y]-lim-b[xx][yy];
    					b[xx][yy]=b[x][y]-lim;
    				}
    				w[b[xx][yy]].emplace_back(make_pair(xx,yy));
    			}
    			b[x][y]=-2;
    		}
    	}
    	return tot<=k;
    }
    int main(){
    	scanf ("%d%d%d",&n,&m,&k);
    	for (int i=1;i<=n;i++) a[i].resize(m+1),b[i].resize(m+1);
    	for (int i=1;i<=n;i++)
    		for (int j=1;j<=m;j++)
    			scanf ("%d",&a[i][j]),mx=max(mx,a[i][j]);
    	int l=0,r=1000,ans=1000;
    	while (l<=r){
    		int mid=(l+r)>>1;
    		if (check(mid)) ans=mid,r=mid-1;
    		else l=mid+1;
    	}
    	printf ("%d",ans);
    	return 0;
    }
    

    E 刀虎二象性

    对每个下标和每种权值分别建一个点,下表并到权值上去
    前两个查询正常做就行,最后一种查询把时间记录在边上,并查集按秩合并

    #include <bits/stdc++.h>
    using namespace std;
    const int N=1000005;
    int fa[N],tim[N],to[N];
    int a[N];
    map <int,int> m,siz,dep;
    int getrt(int x){
    	while (fa[x]) x=fa[x];
    	return x;
    }
    int lca(int u,int v){
    	if (getrt(u)!=getrt(v)) return -1;
    	int now=u,d1=0,d2=0,mx=0;
    	while (now) now=fa[now],d1++;
    	now=v;
    	while (now) now=fa[now],d2++;
    	while (d1>d2) --d1,mx=max(mx,tim[u]),u=fa[u];
    	while (d1<d2) --d2,mx=max(mx,tim[v]),v=fa[v];
        if (u==v) return mx;
    	while (u!=v) {
            mx=max(mx,max(tim[u],tim[v]));
            u=fa[u],v=fa[v];
        }
    	return mx;
    }
    int main(){
    	int T,cnt=0,n=0,ans=0;scanf ("%d",&T);
    	for (int t=1;t<=T;t++){
    		int opt;scanf ("%d",&opt);
    		if (opt==1){
    			int x;scanf ("%d",&x);
    			x^=ans;
    			a[++n]=++cnt;
    			if (!m[x]) {
    				m[x]=++cnt;
    				dep[x]=0,siz[x]=1;
    				to[cnt]=x;
    			}else siz[x]++;
    			fa[a[n]]=m[x];
    			tim[a[n]]=t;
    		}else if (opt==2){
    			int x,y;scanf ("%d%d",&x,&y);
    			x^=ans,y^=ans;
    			if (x==y) continue;
    			if (!m[x]) continue;
    			if (!m[y]) {
    				m[y]=++cnt;
    				to[cnt]=y,dep[y]=0,siz[y]=0;
    			}
    			if (dep[y]<dep[x]) {
    				to[m[x]]=y;
    				swap(dep[x],dep[y]);
    				swap(m[x],m[y]);
    			}
    			siz[y]+=siz[x];
    			dep[y]=max(dep[y],dep[x]+1);
    			fa[m[x]]=m[y];
    			tim[m[x]]=t;
    			to[m[x]]=0;siz[x]=m[x]=dep[x]=0;
    		}else if (opt==3){
    			int x;scanf ("%d",&x);
    			x^=ans;
    			printf ("%d\n",ans=siz[x]);
    		}else if (opt==4){
    			int x;scanf ("%d",&x);
    			x^=ans;
    			printf ("%d\n",ans=to[getrt(a[x])]);
    		}else{
    			int x,y;scanf ("%d%d",&x,&y);
    			x^=ans,y^=ans;
    			if (x==y) printf ("%d\n",ans=tim[a[x]]);
    			else {
    				int p=lca(a[x],a[y]);
    				if (p==-1) puts("-1");
    				else printf ("%d\n",ans=p);
    			}
    		}
    	}
    	return 0;
    }
    

    7.3

    补一下 Codeforces Global Round 21

    C. Fishingprince Plays With Array

    考虑全都拆分到不能再拆分,这样的表示是唯一的,那么都拆分再比较是否完全相同即可

    #include <bits/stdc++.h>
    using namespace std;
    const int N=2e5+5;
    int a[N],b[N];
    long long c1[N],c2[N];
    int main(){
    	int T;scanf ("%d",&T);
    	while (T--){
    		int n,m;scanf ("%d%d",&n,&m);
    		for (int i=1;i<=n;i++) {
    			scanf ("%d",&a[i]);
    			c1[i]=1;
    			while (a[i]%m==0){
    				a[i]/=m;
    				c1[i]*=m;
    			}
    			if (a[i]==a[i-1]) {
    				c1[i-1]+=c1[i];
    				i--;n--;
    			}
    		}
    		int nn;scanf ("%d",&nn);
    		for (int i=1;i<=nn;i++) {
    			scanf ("%d",&b[i]);
    			c2[i]=1;
    			while (b[i]%m==0){
    				b[i]/=m;
    				c2[i]*=m;
    			}
    			if (b[i]==b[i-1]) {
    				c2[i-1]+=c2[i];
    				i--;nn--;
    			}
    		}
    		if (n!=nn) puts("No");
    		else{
    			bool flag=true;
    			for (int i=1;i<=n;i++)
    				if (a[i]!=b[i]||c1[i]!=c2[i]) {
    					flag=false;break;
    				}
    			puts(flag?"Yes":"No");
    		}
    	}
    	return 0;
    }
    

    D. Permutation Graph

    按顺序考虑,令 \(f[i]\)\(1\)\(i\) 的答案,计算 \(f[i]\) 可以由哪些 \(j\) \((j<i)\) 转移过来。
    我们可以通过 \(a[i]\)\(a[i-1]\) 的大小关系确定 \(i\) 的转移是 \([\max,\min]\) 还是 \([\min,\max]\)
    证明一个猜想:只要找最前一个可以转移的端点,就可以保证这个转移是最优的。
    考虑所有可以转移的位置,由于左端点是 \(\max\) 还是 \(\min\) 已经确定下来了,这些位置的值必然是单调的,因此全部跨过用一次转移是不劣的。
    所以只要用单调栈维护所有可能的 \(\max,\min\) 端点即可。

    #include <bits/stdc++.h>
    using namespace std;
    const int N=5e5+5;
    int a[N],f[N];
    int s1[N],t1,s2[N],t2;
    int setmx(int x){
    	for (int i=t2;i>=1;i--)
    		if (a[s2[i]]>a[x]) return s2[i]+1;
    	return 1;
    }
    int setmn(int x){
    	for (int i=t1;i>=1;i--)
    		if (a[s1[i]]<a[x]) return s1[i]+1;
    	return 1;
    }
    int main(){
    	int T;scanf ("%d",&T);
    	while (T--){
    		int n;scanf ("%d",&n);
    		for (int i=0;i<=n;i++) f[i]=1e9;
    		for (int i=1;i<=n;i++) scanf ("%d",&a[i]);
    		f[1]=0;int mnpos=1,mxpos=1;
    		s1[t1=1]=s2[t2=1]=1;
    		for (int i=2;i<=n;i++){
    			f[i]=f[i-1]+1;
    			if (a[i]>a[i-1]){
    				int Lbound=setmx(i);
    				int L=lower_bound(s1+1,s1+t1+1,Lbound)-s1;
    				f[i]=min(f[i],f[s1[L]]+1);
    			}else{
    				int Lbound=setmn(i);
    				int L=lower_bound(s2+1,s2+t2+1,Lbound)-s2;
    				f[i]=min(f[i],f[s2[L]]+1);
    			}
    			while (t1&&a[s1[t1]]>a[i]) --t1;
    			while (t2&&a[s2[t2]]<a[i]) --t2;
    			s1[++t1]=i,s2[++t2]=i;
    		}
    		printf ("%d\n",f[n]);
    	} 
    	return 0;
    }
    

    E. Placing Jinas

    可以发现递推式就是杨辉三角,所以第 \(i\) 行第 \(j\) 列的方案数是 \(\binom{i+j}{i}\)
    现在是一堆这个东西求和,那么枚举行,快速计算一列的和即可。
    计算一列的和用到了组合恒等式:

    \[\sum_{i=k}^n\binom{i}{k}=\binom{n+1}{k+1} \]

    组合意义是考虑 \(n+1\) 个位置里面,选择 \(k+1\) 个位置,扔掉第一个位置以及它前面的所有,在后面剩下的就是选 \(k\) 个的方案数。

    #include <bits/stdc++.h>
    using namespace std;
    const int N=500005,Mod=1e9+7;
    inline int qpow(int a,int b){
    	int ans=1;
    	while (b){
    		if (b&1) ans=1ll*ans*a%Mod;
    		a=1ll*a*a%Mod;b>>=1;
    	}
    	return ans;
    }
    int a[N],fac[N],inv[N];
    int C(int n,int m){
    	if (n<0||m<0||n<m) return 0;
    	return 1ll*fac[n]*inv[m]%Mod*inv[n-m]%Mod; 
    }
    int main(){
    	int n;scanf ("%d",&n);fac[0]=1;
    	for (int i=1;i<=500000;i++) fac[i]=1ll*fac[i-1]*i%Mod;
    	inv[500000]=qpow(fac[500000],Mod-2);
    	for (int i=499999;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%Mod;
    	int ans=0;
    	for (int i=0;i<=n;i++){
    		int a;scanf ("%d",&a);
    		if (a==0) break;
    		ans=(ans+C(a+i,i+1))%Mod;
    	}
    	printf ("%d",ans);
    	return 0;
    }
    

    F. Tree Recovery

    很妙的题。我们可以知道,如果树中存在相邻的三个点 \(x,y,z\)\((x,y)\)\((y,z)\) 是树中的边,那么 \(d(y,x)=d(y,z)\) 一定存在,这是显然的。
    但是,上面的情况仅表示距离为 \(1\) 的情况,距离为 \(2\) 或更大会对这个性质产生干扰。
    我们考虑这样的做法:把一个点对看成点,相邻且距离相等的点对之间连边,建立新图。那么距离为 \(1\) 的那种情况建立出来的子图一定不会和其他部分相交。所以遍历每个连通块 check 即可。
    check 的次数不超过 \(n\) 次,时间复杂度为 \(O(n^4)\) ,可以 bitset 优化为 \(\frac{n^4}{\omega}\)
    写代码的时候没有考虑清楚一些细节,比如写完才发现新图不会存在回路,所以有一些多余的特判

    #include <bits/stdc++.h>
    using namespace std;
    const int N=205;
    int Head[N],Next[N<<1],Adj[N<<1],tot=0;
    char s[N];int to[N][N],U[N*N],V[N*N],cnt=0;
    vector <int> v[N*N];
    int fa[N];
    int getrt(int x){
    	return fa[x]==x?x:fa[x]=getrt(fa[x]);
    }
    void addedge(int u,int v){
    	Next[++tot]=Head[u],Head[u]=tot,Adj[tot]=v;
    	Next[++tot]=Head[v],Head[v]=tot,Adj[tot]=u;
    }
    bool flag;
    void merge(int x,int y){
    	if (getrt(x)==getrt(y)){
    		flag=false;
    		return;
    	}
    	fa[getrt(x)]=getrt(y);
    	addedge(x,y);
    }
    bool vis[N*N];
    int n;
    void dfs(int x){
    	vis[x]=true;merge(U[x],V[x]);
    	for (int i=0;i<v[x].size();i++)
    		if (!vis[v[x][i]]) dfs(v[x][i]);
    }
    void init(){
    	flag=true;
    	for (int i=1;i<=n;i++) fa[i]=i;
    	memset (Head,0,sizeof(Head));
    	for (int i=1;i<=tot;i++) Next[i]=Adj[i]=0;
    	tot=0;
    }
    char st[N][N][N];
    int dis[N][N];
    void getdist(int rt,int x,int f,int d){
    	dis[rt][x]=d;
    	for (int e=Head[x];e;e=Next[e])
    		if (Adj[e]!=f) getdist(rt,Adj[e],x,d+1);
    }
    bool check(){
    	if (!flag) return false;
    	if (tot!=n*2-2) return false;
    	for (int i=1;i<=n;i++)
    		for (int j=1;j<=n;j++)
    			dis[i][j]=-1;
    	for (int i=1;i<=n;i++) getdist(i,i,0,0);
    	for (int i=1;i<n;i++)
    		for (int j=i+1;j<=n;j++)
    			for (int k=1;k<=n;k++)
    				if ((dis[i][k]==dis[j][k])^(st[i][j][k]-'0')) return false;
    	return true;
    }
    void print(){
    	int num=0;
    	for (int i=1;i<=n;i++)
    		for (int e=Head[i];e;e=Next[e])
    			if (e&1) {
    				printf ("%d %d\n",i,Adj[e]);
    				num++;
    			}
    	assert(num==n-1);
    }
    int main(){
    	int T;scanf ("%d",&T);
    	while (T--){
    		scanf ("%d",&n);cnt=0;
    		for (int i=1;i<=n;i++)
    			for (int j=1;j<=i;j++)
    				to[i][j]=++cnt,U[cnt]=i,V[cnt]=j;
    		for (int i=1;i<=n;i++)
    			for (int j=i+1;j<=n;j++)
    				to[i][j]=to[j][i];
    		for (int i=1;i<=cnt;i++) vis[i]=false;
    		for (int i=1;i<=cnt;i++) v[i].clear();
    		for (int i=1;i<n;i++)
    			for (int j=i+1;j<=n;j++){
    				scanf ("%s",s+1);
    				for (int k=1;k<=n;k++){
    					if (s[k]=='1') {
    						v[to[i][k]].emplace_back(to[j][k]);
    						v[to[j][k]].emplace_back(to[i][k]);
    					}
    					st[i][j][k]=s[k];
    				}
    			}
    		for (int i=1;i<=cnt;i++)
    			if (!vis[i]) {
    				init();
    				dfs(i);
    				if (check()) {
    					puts("Yes");
    					print();
    					flag=true;
    					break;
    				}
    				flag=false;
    			}
    		if (!flag) puts("No");
    	}
    	return 0;
    }
    
  • 相关阅读:
    webstrom 内存溢出,软件崩溃卡死解决的方法
    JAVA 基础 / 第八课:面向对象 / JAVA类的方法与实例方法
    JAVA 基础 /第七课: 面向对象 / JAVA类的属性,类变量与实例变量
    JAVA 基础 /第六课: 面向对象 / JAVA中的类和对象
    JAVA 基础 /第五课:ECLIPSE常见的使用技巧以及部分快捷键
    JAVA 基础 / 第四课:在ECLIPSE中运行第一个 JAVA 程序以及找不到类的问题
    JAVA 基础 /第三课:下载 ECLIPSE并使用ECIPSE创建第一个 JAVA PROJECT
    JAVA 基础 / 第二课:用命令行中编写第一个 JAVA 程序
    JAVA 基础 / 第一课:手把手教你做JDK环境变量配置
    Java swing中的keyListener使用事例
  • 原文地址:https://www.cnblogs.com/ehuohz/p/16413816.html
Copyright © 2020-2023  润新知