zoukankan      html  css  js  c++  java
  • SSF信息社团3月训练题目整理

    Hint:可以点击右下角的目录符号快速跳转到指定位置

    上接:SSF信息社团寒假训练题目整理(三)
    下接:SSF信息社团4月训练题目整理

    3.5

    995A

    Link

    先将能归位的车都归位,然后让所有车按照图中顺序不断地转:

    6mSH9P.png

    每次转完后都检查每辆车是否能归位即可。最坏情况是每辆车都转了一圈,即 (2n imes 2n=10000) 次操作,再加上进入车位的操作数,共 (10000+100<20000) 次,可以通过。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int N=50;
    int a[5][N+10];
    int n,k;
    vector<pair<int,pair<int,int> > > ans;
    void print(pair<int,pair<int,int> > x) 
    {printf("%d %d %d
    ",x.first,x.second.first,x.second.second);}
    pair<int,pair<int,int> > Make(int x,int y,int z) 
    {return make_pair(x,make_pair(y,z));}
    void see()
    {
    	puts("--------");
    	for(int i=2;i<=3;i++)
    	{
    		for(int j=1;j<=n;j++) printf("%d ",a[i][j]);
    		puts("");
    	}
    	puts("========");
    }
    void move()
    {
    	for(int i=2;i<=3;i++)
    	{
    		for(int j=1;j<=n;j++)
    		{
    			if(!a[i][j]) continue;
    			if(i==2&&a[1][j]==a[2][j])
    			{
    				ans.push_back(Make(a[2][j],1,j));
    				a[2][j]=0;
    			}
    			if(i==3&&a[4][j]==a[3][j])
    			{
    				ans.push_back(Make(a[3][j],4,j));
    				a[3][j]=0;
    			}
    		}
    	}
    }
    pair<int,int> nxt(int i,int j)
    {
    	if(i==2)
    	{
    		if(j==n) return make_pair(3,n);
    		else return make_pair(2,j+1);
    	}
    	else
    	{
    		if(j==1) return make_pair(2,1);
    		else return make_pair(3,j-1);
    	}
    }
    int &nxta(int i,int j){return a[nxt(i,j).first][nxt(i,j).second];}
    pair<int,int> pre(int i,int j)
    {
    	if(i==2)
    	{
    		if(j==1) return make_pair(3,1);
    		else return make_pair(2,j-1);
    	}
    	else
    	{
    		if(j==n) return make_pair(2,n);
    		else return make_pair(3,j+1);
    	}
    
    }
    int &prea(int i,int j){return a[pre(i,j).first][pre(i,j).second];}
    void rotate()
    {
    	int fi=0,fj=0;
    	for(int i=2;i<=3;i++)
    	{
    		for(int j=1;j<=n;j++)
    		{
    			if(!a[i][j]&&nxta(i,j))
    			{
    				fi=i;
    				fj=j;
    				break;
    			}
    		}
    		if(fi||fj) break;
    	}
    	for(int ii=nxt(fi,fj).first,jj=nxt(fi,fj).second;ii!=fi||jj!=fj;)
    	{
    		prea(ii,jj)=a[ii][jj];
    		if(a[ii][jj])
    			ans.push_back(Make(a[ii][jj],pre(ii,jj).first,pre(ii,jj).second));
    		a[ii][jj]=0;
    		pair<int,int> tmp=nxt(ii,jj);
    		ii=tmp.first;
    		jj=tmp.second;
    	}
    }
    bool check()
    {
    	for(int i=2;i<=3;i++)
    	{
    		for(int j=1;j<=n;j++)
    			if(a[i][j])
    				return false;
    	}
    	return true;
    }
    bool check1()
    {
    	for(int i=2;i<=3;i++)
    	{
    		for(int j=1;j<=n;j++)
    			if(!a[i][j])
    				return true;
    	}
    	return false;
    }
    int main()
    {
    	scanf("%d %d",&n,&k);
    	for(int i=1;i<=4;i++)
    		for(int j=1;j<=n;j++)
    			scanf("%d",&a[i][j]);
    	move();
    	if(!check1()) return printf("-1"),0;
    	while(!check())
    	{
    		rotate();
    		move();
    	}
    	printf("%d
    ",(int)ans.size());
    	for(int i=0;i<ans.size();i++) print(ans[i]);
    	return 0;
    }
    

    993C

    Link

    战机有可能在的位置只能在左边某个点和右边某个点的中点上,最多 (n imes m=360) 个。对每一个可能的点用 bitset/long long 存一下两边哪些点的中点在这里,然后 (n^2m^2) 暴力枚举战机在哪两个点并计算覆盖到了两边哪些点即可。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<set>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    int cal(ll n)
    {
    	int ans=0;
    	while(n)
    	{
    		ans++;
    		n-=(n&-n);
    	}
    	return ans;
    }
    const int N=60;
    int a[N+10],b[N+10];
    ll sx[40000+10],sy[40000+10];
    int main()
    {
    	int n,m;
    	scanf("%d %d",&n,&m);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	for(int i=1;i<=m;i++) scanf("%d",&b[i]);
    	set<int> s;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=m;j++)
    		{
    			sx[a[i]+b[j]+20000]|=(1ll<<i-1);
    			sy[a[i]+b[j]+20000]|=(1ll<<j-1);
    			s.insert(a[i]+b[j]);
    		}
    	}
    	int ans=0;
    	for(set<int>::iterator it1=s.begin();it1!=s.end();it1++)
    	{
    		for(set<int>::iterator it2=s.begin();it2!=s.end();it2++)
    		{
    			int tx=*it1,ty=*it2;
    			ans=max(ans,cal(sx[tx+20000]|sx[ty+20000])+cal(sy[tx+20000]|sy[ty+20000]));
    		}
    	}
    	printf("%d",ans);
    	return 0;
    }
    

    1000E

    Link

    容易观察出必须经过的点一定是桥(割边)。把边双缩一下点,得到一棵树,求树的直径即可。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    using namespace std;
    const int N=6e5,M=6e5;
    int head[N+10],ver[M+10],nxt[M+10],tot=1;
    void add(int x,int y)
    {
    	ver[++tot]=y;
    	nxt[tot]=head[x];
    	head[x]=tot;
    }
    bool f[N+10];
    int low[N+10],dfn[N+10],num;
    void tarjan(int x,int in)
    {
    	dfn[x]=low[x]=++num;
    //	printf("x:%d
    ",x);
    	for(int i=head[x];i;i=nxt[i])
    	{
    		int y=ver[i];
    		if(!dfn[y])
    		{
    			tarjan(y,i);
    			low[x]=min(low[x],low[y]);
    			if(low[y]>low[x]) f[i]=f[i^1]=1;
    		}
    		else if(i!=(in^1))
    			low[x]=min(low[x],dfn[y]);
    	}
    }
    int col[N+10];
    void dfs(int x,int co)
    {
    	col[x]=co;
    	for(int i=head[x];i;i=nxt[i])
    	{
    		if(f[i]) continue;
    		int y=ver[i];
    		if(!col[y])
    			dfs(y,co);
    	}
    }
    int Head[N+10],Ver[M+10],Nxt[M+10],Tot=0;
    void Add(int x,int y)
    {
    	Ver[++Tot]=y;
    	Nxt[Tot]=Head[x];
    	Head[x]=Tot;
    }
    bool vis[N+10];
    int dis[N+10];
    pair<int,int> bfs(int x)
    {//first:distance, second:vertex
    	memset(dis,0,sizeof(dis));
    	memset(vis,0,sizeof(vis));	
    	pair<int,int> ans=make_pair(0,0);
    	queue<pair<int,int> > que;
    	que.push(make_pair(0,x));
    	vis[x]=1;
    	while(!que.empty())
    	{
    		pair<int,int> x=que.front(); que.pop();
    		ans=max(ans,x);
    		for(int i=Head[x.second];i;i=Nxt[i])
    		{
    			int y=Ver[i];
    			if(vis[y]) continue;
    			vis[y]=1;
    			dis[y]=dis[x.second]+1;
    			que.push(make_pair(dis[x.second]+1,y));
    		}
    	}
    	return ans;
    }
    int main()
    {
    	int n,m;
    	scanf("%d %d",&n,&m);
    	for(int i=1;i<=m;i++)
    	{
    		int x,y;
    		scanf("%d %d",&x,&y);
    		add(x,y);
    		add(y,x);
    	}
    	for(int i=1;i<=n;i++)
    		if(!dfn[i])
    			tarjan(i,0);
    //	for(int i=2;i<=tot;i+=2)
    //		if(f[i])
    //			printf("%d->%d
    ",ver[i^1],ver[i]);
    	int cnt=0;
    	for(int i=1;i<=n;i++)
    		if(!col[i])
    			dfs(i,++cnt);
    //	for(int i=1;i<=n;i++) printf("col[%d]:%d
    ",i,col[i]);
    //	puts("");
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=head[i];j;j=nxt[j])
    			if(col[i]!=col[ver[j]])
    				Add(col[i],col[ver[j]]);
    	}
    //	for(int i=1;i<=cnt;i++)
    //	{
    //		printf("i=%d:",i);
    //		for(int j=Head[i];j;j=Nxt[j]) printf("%d ",Ver[j]);
    //		puts("");
    //	}
    	printf("%d",bfs(bfs(1).second).first);
    	return 0;
    }
    

    3.6

    1433F

    Link

    对每一行进行 dp,令 (f(i,j,k)) 表示当前一行考虑前 (i) 列,共选了 (j) 个数,和余数为 (k) 的和的最大值。随便转移一下求出所有 (lin[0,k-1])(f(m,j,l)) 的最大值,把这 (k) 个最大值就相当于第 (i) 行的物品,那么题意变成了:已知有 (n) 组带权值物品,每组物品都有 (k) 个,每个组只能选一个物品,求在所有物品权值的和是 (k) 的倍数的情况下和的最大值。令 (dp(i,j)) 表示前 (i) 组,当前和的余数为 (j) 的答案,转移一下,(dp(n,0)) 就是答案。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    using namespace std;
    inline void read(int &x)
    {
    	x=0; int f=1;
    	char c=getchar();
    	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    	x*=f;
    }
    const int N=80;
    int f[N][N][N],tmp[N];
    int a[N][N],n,m,k;
    vector<int> v[N];
    void Dp(int r)//row
    {
    	memset(f,-0x3f,sizeof(f));
    //	f[0][0][0]=0;
    	for(int i=0;i<=m;i++) f[i][0][0]=0;
    	for(int i=1;i<=m;i++)
    	{
    		for(int j=1;j<=m/2;j++)
    		{
    			for(int l=0;l<k;l++)
    			{
    				f[i][j][l]=f[i-1][j][l];
    				if(j) f[i][j][l]=max(f[i][j][l],f[i-1][j-1][((l-a[r][i])%k+k)%k]+a[r][i]);
    //				printf("((%d-%d)mod%d+%d)mod%d:%d
    ",l,a[r][i],k,k,k,((l-a[r][i])%k+k)%k);
    			}
    		}
    	}
    	memset(tmp,-0x3f,sizeof(tmp));
    	for(int j=0;j<=m/2;j++)
    		for(int l=0;l<k;l++)
    			tmp[l]=max(tmp[l],f[m][j][l]);
    	for(int i=0;i<k;i++) v[r].push_back(tmp[i]);
    }
    int dp[N][N];
    int main()
    {
    	read(n);read(m);read(k);
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			read(a[i][j]);
    	for(int i=1;i<=n;i++) Dp(i);
    //	for(int i=1;i<=n;i++)
    //	{
    //		printf("i:%d
    ",i);
    //		for(int j=0;j<v[i].size();j++) printf("%d ",v[i][j]);
    //		puts("");
    //	}
    	memset(dp,-0x3f,sizeof(dp));
    	for(int i=0;i<=n;i++)
    		dp[i][0]=0;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=0;j<k;j++)
    		{
    			dp[i][j]=dp[i-1][j];
    			for(int l=0;l<v[i].size();l++)
    				dp[i][j]=max(dp[i][j],dp[i-1][((j-v[i][l])%k+k)%k]+v[i][l]);
    		}
    	}
    	printf("%d",dp[n][0]);
    	return 0;
    }
    

    1433G

    Link

    做过,丢链接跑路

    1446C

    Link

    题目等价于问最大保留数量使得有且仅有一对数的边重复。把所有 (a_i) 插到 Trie 树里,则与 (a_i) 异或值最小的点 (a_j) 一定在 (operatorname{LCA}(a_i,a_j)) 里,且 (operatorname{LCA}(a_i,a_j)) 是所有 (operatorname{LCA}(a_i,a_k);(k ot=i)) 中最小的。为了有且仅有一对数的边重复,需要把 Trie 树变成这样:

    考虑 dp。令 (f(i)) 表示以 (i) 为根的子树的最大保留数量,那么有转移:

    [f(i)=egin{cases}max{f(lson),f(rson)}+1, &lson,rson ext{ 都存在}\f(lson), & ext{只存在 }lson\f(rson), & ext{只存在 } rsonend{cases} ]

    答案就是 (n-f(0))(0) 为 Trie 的根)。

    #include<bits/stdc++.h>
    using namespace std;
    inline void read(int &x)
    {
    	x=0; int f=1;
    	char c=getchar();
    	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    	x*=f;
    }
    const int N=2e5+10;
    int tr[N*30][2],cnt=0;
    void ins(int x)
    {
    	int pos=0;
    	for(int i=30;i>=0;i--)
    	{
    		bool z=x&(1<<i);
    		if(!tr[pos][z])
    			tr[pos][z]=++cnt;
    		pos=tr[pos][z];
    //		sz[pos]++; 
    	}
    }
    int f[N*30];
    void Dp(int pos)
    {
    	if(!tr[pos][0]&&!tr[pos][1])
    	{
    		f[pos]=1;
    		return;
    	}
    	if(tr[pos][0]&&tr[pos][1])
    	{
    		Dp(tr[pos][0]);
    		Dp(tr[pos][1]);
    		f[pos]=max(f[tr[pos][0]],f[tr[pos][1]])+1;
    		return;
    	}
    	if(tr[pos][0])
    	{
    		Dp(tr[pos][0]);
    		f[pos]=max(f[pos],f[tr[pos][0]]);
    	} 
    	if(tr[pos][1])
    	{
    		Dp(tr[pos][1]);
    		f[pos]=max(f[pos],f[tr[pos][1]]);
    	}
    }
    int a[N]; 
    int main()
    {
    	int n;
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) 
    	{
    		scanf("%d",&a[i]);
    		ins(a[i]);
    	}
    	Dp(0);
    	printf("%d",n-f[0]);
    	return 0;
    }
    

    3.7

    319B

    Link

    维护一个栈底到栈顶递增的单调栈,令 (f(i)) 表示神经 病 (i) 需要几轮才能被杀死(若不死则 (f(i)=0)),则:

    • while(top&&a[st[top]]<a[i]) top-- 操作执行完后栈为空,(f(i)=0),这个人死不了。
    • 若一个元素都没弹出,(f(i)=1),因为会被他下面的神经病直接杀死。
    • 若弹出了 (k) 个人((k>0)(j_1,j_2,cdots,j_k)(f(i)=maxlimits_{l=1}^k{f(j_l)}),因为栈底需要杀死这 (k) 个神经病 (i) 才能死。

    答案为 (maxlimits_{i=1}^n{f(i)})

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+10;
    int a[N];
    int st[N],top=0;
    int main()
    {
    	int n;
    	scanf("%d",&n);
    	vector<int> f(n+1);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	int ans=0;
    	for(int i=1;i<=n;i++)
    	{
    		int mx=0;
    		while(top&&a[st[top]]<a[i]) 
    		{
    			mx=max(mx,f[st[top]]);
    			top--;
    		}
    		if(!top)
    			f[i]=0;
    		else
    			f[i]=mx+1;
    		st[++top]=i;
    		ans=max(ans,f[i]);
    	}
    	printf("%d",ans);
    	return 0;
    }
    

    321B

    Link

    考虑两种贪心策略:

    1. 打完手牌,也就是打消耗战,先把对方的 DEF 消耗完,然后把 ATK 消耗完,最后打真伤害。消耗过程中要选择与对方手牌点数尽量近的,因为要使真伤害尽量大;
    2. 不打完手牌,只打对方的 ATK,每次尽量用大的打小的。

    可以发现两种贪心策略如果单独使用都会挂,所以要同时使用然后取 (max)

    #include<bits/stdc++.h>
    using namespace std;
    const int N=100+10;
    #define int long long
    int atk[N],def[N],a[N];
    int ca,cd,n,m;
    bool vis[N];
    int sol1()
    {
    	if(m<n) return -1;
    	int ans=0,cnt=0;
    	for(int i=1,j=1;i<=m&&j<=cd;j++,i++)
    	{
    		while(a[i]<=def[j])
    			i++;
    		vis[i]=1;
    		cnt++;
    	}
    	// puts("VIS:");
    	// for(int i=1;i<=m;i++) putchar('0'^vis[i]);
    	// puts("");
    	for(int i=1,j=1;i<=m&&j<=ca;j++,i++)
    	{
    		while(a[i]<atk[j]||vis[i])
    		{
    			i++;
    			if(i>m) break;
    		}
    		if(i>m) break;
    		vis[i]=1;
    		ans+=a[i]-atk[j];
    		cnt++;
    	}
    	// printf("cnt:%lld
    ",cnt);
    	if(cnt!=n) return -1;
    	for(int i=1;i<=m;i++)
    		if(!vis[i])
    			ans+=a[i];
    	return ans;
    }
    int sol2()
    {
    	int ans=0;
    	// memset(vis,0,sizeof(vis));
    	for(int i=m,j=1;i>=1&&j<=ca;j++,i--)
    	{
    		// printf("i=%lld, j=%lld
    ",i,j);
    		// printf("a[i]:%lld, atk[j]:%lld
    ",a[i],atk[j]);
    		while(a[i]<atk[j]&&i>0)
    			i--;
    		if(i<=0) continue;
    		ans+=a[i]-atk[j];
    	}
    	return ans;
    }
    signed main()
    {
    	scanf("%lld %lld",&n,&m);
    	for(int i=1;i<=n;i++)
    	{
    		char s[5];
    		int x;
    		scanf("%s%lld",s,&x);
    		if(!strcmp(s,"ATK"))
    			atk[++ca]=x;
    		else
    			def[++cd]=x;
    	}
    	for(int i=1;i<=m;i++)
    		scanf("%lld",&a[i]);
    	sort(a+1,a+m+1);
    	sort(atk+1,atk+ca+1);
    	sort(def+1,def+cd+1);
    	// printf("sol1:%lld, sol2:%lld
    ",sol1(),sol2());
    	printf("%lld",max(sol1(),sol2()));
    	return 0;
    }
    

    327D

    Link

    要尽量放 red towers。考虑一种贪心,对图进行 DFS,如果当前到达的点 ((i,j)) 不是 big hole,就先放一个 blue tower,然后往后面递归,递归回来之后递归树内的儿子节点都变成了红的,此时把 ((i,j)) 的 blue tower 搞掉,放成红的。至于儿子怎么处理,请再读一遍刚才那句话。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    using namespace std;
    const int N=500;
    char s[N+10][N+10];
    bool vis[N+10][N+10];
    int n,m;
    vector<pair<char,pair<int,int> > > ans;
    pair<char,pair<int,int> > make(char s,int a,int b) {return make_pair(s,make_pair(a,b));}
    void print(pair<char,pair<int,int> > x) {printf("%c %d %d
    ",x.first,x.second.first,x.second.second);}
    const int nxt[4][2]={0,1,1,0,0,-1,-1,0};
    void dfs(int x,int y,bool flag)
    {
    	vis[x][y]=1;
    	ans.push_back(make('B',x,y));
    	for(int i=0;i<4;i++)
    	{
    		int tx=nxt[i][0]+x,ty=nxt[i][1]+y;
    		if(tx<1 || tx>n || ty<1 || ty>m || vis[tx][ty] || s[tx][ty]=='#') continue;
    		// printf("tx:%d, ty:%d
    ",tx,ty);
    		// vis[tx][ty]=1;
    		dfs(tx,ty,false);
    	}
    	if(!flag)
    	{
    		ans.push_back(make('D',x,y));
    		ans.push_back(make('R',x,y));
    	}
    }
    int main()
    {
    	scanf("%d %d",&n,&m);
    	for(int i=1;i<=n;i++)
    		scanf("%s",s[i]+1);
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++)
    			if(s[i][j]=='.'&&!vis[i][j])
    				dfs(i,j,1);
    	printf("%d
    ",(int)ans.size());
    	for(int i=0;(unsigned)i<ans.size();i++) print(ans[i]);
    	return 0;
    }
    

    3.8

    724D

    Link

    注意到一个答案一定是形如下面的形式:(mathtt{aadots aabbdots bbdots zzz}),也就是除了最后一个字符,其他字典序小于它的字符都放进答案里。可以通过 (Theta(26n)) 的时间枚举出最后一个字符是谁,不妨设为 (x),然后在字符串 (s) 中把所有字典序 (<x) 的字符都标记出来,这些都是必定在答案中的,然后通过贪心来枚举最少放多少个 (x) 能使整个字符串满足条件。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int N=1e5;
    char s[N+10];bool vis[N+10];
    int n,m;
    int get()
    {
    	for(int i=0;i<26;i++)
    	{
    		for(int j=1;j<=n;j++)
    			if(s[j]-'a'<=i)
    				vis[j]=1;
    		int lst=0,mx=0;
    		for(int j=1;j<=n;j++)
    		{
    			if(vis[j])
    			{
    				mx=max(mx,j-lst);
    				lst=j;
    			}
    		}
    		mx=max(mx,n-lst);
    		// printf("i:%d, mx:%d
    ",i,mx);
    		if(mx<=m) return i;
    	}
    }
    int c[26];
    // bool vis[N+10];
    int dis[N+10];
    int main()
    {
    	scanf("%d%s",&m,s+1);
    	n=strlen(s+1);
    	for(int i=1;i<=n;i++)
    		c[s[i]-'a']++;
    	int k=get();
    	int lst=0;
    	int cnt=0,lstvis=0;
    	memset(vis,0,sizeof(vis));	// for(int i=1;i<=n;i++)
    	// 	printf("%d",vis[i]);
    	for(int i=1;i<=n;i++)
    		if(s[i]-'a'<k)
    			vis[i]=1;
    	vector<int> v;
    	for(int i=1;i<=n;i++)
    		if(s[i]-'a'==k)
    			v.push_back(i);
    	// for(int i=0;i<v.size();i++) printf("%d ",v[i]);
    	// puts("");
    	for(int i=1,j=0;i<=n;i++)
    	{
    		if(vis[i]) lst=i;
    		if(i-lst>=m)
    		{
    			while(j<v.size()&&v[j]<=i)
    				j++;
    			j--;
    			lst=v[j];
    			cnt++;
    		}
    	}
    	for(int i=0;i<k;i++)
    		for(int j=1;j<=c[i];j++)
    			putchar('a'+i);
    	for(int i=1;i<=cnt;i++)
    		putchar('a'+k);
    	return 0;
    }
    

    729E

    Link

    合法的序列排序后一定是这样的:(0,1,1,2,2,3,3,cdots,x)(forall iin[1,x],exists a_{j}=x-1)。于是可以把这些数都放到一个桶里,贪心地把最大的、多余的数补给到较小的数中。要注意特判 (a_s ot=0) 或者 (exists i ot=s,a_i=0) 的情况。

    #include<bits/stdc++.h>
    using namespace std;
    const int N=2e5+10;
    int a[N],cnt[N];
    int n,k;
    int main()
    {
    	scanf("%d %d",&n,&k);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	int ans=0;
    	int c=0;
    	for(int i=1;i<=n;i++)
    	{
    		if(!a[i] && i!=k)
    			c++;
    		else if(i!=k) cnt[a[i]]++;
    	}
    	ans+=c;
    	if(a[k]) ans++;
    	int j = n-1;
    	while(!cnt[j]) j--;
    	for(int i=1;i<=n-1&&j>=1&&i<j;i++)
    	{
    		if(cnt[i]) continue;
    		if(c)
    		{
    			c--;
    			continue;
    		}
    		while(!cnt[j]) j--;
    		if(i>=j) break;
    		cnt[i]++;
    		cnt[j]--;
    		ans++;
    		// out();
    	}
    	printf("%d",ans);
    	return 0;
    }
    

    811C

    Link

    使用 (mathcal O(n^2)) 的时间复杂度预处理每个区间 ([l,r]) 是否合法,如果合法就顺便处理出 (a_loperatorname{xor}a_{l+1}operatorname{xor}cdotsoperatorname{xor}a_r)。令 (f(i)) 表示从 (1)(i) 分割出若干个区间能得到的最优答案,令 (f(0)=0),则有:

    [f(i)=maxleft{f(j)+[ ext{区间 }j+1sim i ext{ 合法}]cdotoperatorname{xor}limits_{j=0}^{i-1}a_j ight} ]

    由于 (nle 5000)(mathcal O(n^2)) 暴力转移即可,(f(n)) 即为答案。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    inline void read(int &x)
    {
    	x=0; int f=1;
    	char c=getchar();
    	while(c<'0' || c>'9') {if(c=='-') f=-1;c=getchar();}
    	while(c>='0' && c<='9') {x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    	x*=f;
    }
    const int N=5010;
    int sum[N][N];
    bool f[N][N];
    int a[N],dp[N];
    int cnt[N],cnt1[N];
    int main()
    {
    	int n;
    	read(n);
    	for(int i=1;i<=n;i++) 
    	{
    		read(a[i]);
    		cnt[a[i]]++;
    	}
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=5000;j++) cnt1[j]=0;
    		int zzt=0;
    		for(int j=i;j<=n;j++)
    		{
    			sum[i][j]=sum[i][j-1];
    			if(!cnt1[a[j]]) sum[i][j]^=a[j];
    			if(!cnt1[a[j]]) zzt++;
    			cnt1[a[j]]++;
    			if(cnt1[a[j]]==cnt[a[j]]) zzt--;
    			if(zzt==0) f[i][j]=1;
    		}
    	}
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=0;j<i;j++)
    		{
    			dp[i]=max(dp[i],dp[j]);
    			if(f[j+1][i]) dp[i]=max(dp[i],dp[j]+sum[j+1][i]);
    		}
    	}
    	printf("%d",dp[n]);
    	return 0;
    }
    

    3.9

    835D

    Link

    (f(i,j)) 表示 ([i,j]) 的回文阶数。若 (s_{i}s_{i+1}cdots s_j) 不是回文串(用哈希判),则 (f(i,j)) 设为 (0);否则,先把 (f(i,j)) 设成 (1),如果左半部分和右半部分相等,就把左半部分的 (f) 值加上 (1) 再和 (f(i,j)) 取个 (max)。最后遍历一遍所有 (f(i,j)),每个 (f(i,j)) 相当于给 (1sim f(i,j)) 的答案加了 (1),随便统计一下即可。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    typedef unsigned long long ull;
    const ull base=20061269ull;
    const int N=5010;
    ull p[N],h1[N],h2[N];
    char s[N];
    int n;
    void init()
    {
    	p[0]=1ull;
    	for(int i=1;i<=n;i++)
    	{
    		p[i]=p[i-1]*base;
    		h1[i]=h1[i-1]*base+s[i];
    	}
    	for(int i=n;i;i--) h2[i]=h2[i+1]*base+s[i];
    }
    ull hs1(int l,int r) {return h1[r]-h1[l-1]*p[r-l+1];}
    ull hs2(int l,int r) {return h2[l]-h2[r+1]*p[r-l+1];}
    int dp[N][N];
    int c[N];
    void modify(int x,int d) {for(;x<=n;x+=x&-x) c[x]+=d;}
    int query(int x) {int ans=0;for(;x;x-=x&-x)ans+=c[x];return ans;}
    int main()
    {
    	scanf("%s",s+1);
    	n=strlen(s+1);
    	init();
    	for(int i=1;i<=n;i++) dp[i][i]=1;
    	for(int len=2;len<=n;len++)
    	{
    		for(int i=1;i+len-1<=n;i++)
    		{
    			int j=i+len-1;
    			if(hs1(i,j)==hs2(i,j)) 
    			{
    				dp[i][j]=1;
    				if(hs1(i,i+len/2-1)==hs2(j-len/2+1,j))
    					dp[i][j]=max(dp[i][j],dp[i][i+len/2-1]+1);
    			}
    		}
    	}
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=i;j<=n;j++)
    		{
    			modify(dp[i][j]+1,-1);
    			modify(1,1);
    		}
    		// puts
    	}
    	for(int i=1;i<=n;i++) printf("%d ",query(i));
    	return 0;	 
    }
    

    832D

    Link

    找一下 (a,b,c) 三个点的分叉点,观察图可知((1) 为根,(a=1,b=3,c=4)),实际上就是找 (operatorname{lca}(a,b),operatorname{lca}(b,c),operatorname{lca}(a,c)) 取深度最大的。设这个点为 (d),那么答案就是 (max{operatorname{dis}(d,a),operatorname{dis}(d,b),operatorname{dis}(d,c)}+1)

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<queue>
    using namespace std;
    const int N=1e5+10,M=2e5+10;
    int head[N],ver[M],nxt[M],tot=0;
    void add(int x,int y)
    {
    	ver[++tot]=y;
    	nxt[tot]=head[x];
    	head[x]=tot;
    }
    int t,n,dep[N],fa[N][30];
    bool vis[N];
    void init()
    {
    	queue<int> que;
    	que.push(1);
    	dep[1]=0;
    	vis[1]=1;
    	while(!que.empty())
    	{
    		int x=que.front();que.pop();
    		for(int i=head[x];i;i=nxt[i])
    		{
    			int y=ver[i];
    			if(vis[y]) continue;
    			vis[y]=1;
    			dep[y]=dep[x]+1;
    			fa[y][0]=x;
    			for(int i=1;i<=t;i++)
    				fa[y][i]=fa[fa[y][i-1]][i-1];
    			que.push(y);
    		}
    	}
    }
    int lca(int x,int y)
    {
    	if(dep[x]<dep[y]) swap(x,y);
    	for(int i=t;~i;i--)
    		if(dep[fa[x][i]]>=dep[y])
    			x=fa[x][i];
    	if(x==y) return x;
    	for(int i=t;~i;i--)
    	{
    		if(fa[x][i]!=fa[y][i])
    		{
    			x=fa[x][i];
    			y=fa[y][i];
    		}
    	}
    	return fa[x][0];
    }
    int dis(int x,int y) {return dep[x]+dep[y]-2*dep[lca(x,y)];}
    void ckmx(int &x,int y) {x=max(x,y);}
    int main()
    {
    	int n,q;
    	scanf("%d %d",&n,&q);
    	t=(int)(log(n)/log(2))+1;
    	for(int i=2;i<=n;i++)
    	{
    		int x;
    		scanf("%d",&x);
    		add(x,i);
    		add(i,x);
    	}
    	init();
    	for(int i=1;i<=q;i++)
    	{
    		int x,y,z;
    		scanf("%d %d %d",&x,&y,&z);
    		int l1=lca(x,y),l2=lca(x,z),l3=lca(y,z),tmp=0;
    		if(dep[l1]>=dep[l2]&&dep[l1]>=dep[l3]) 
    			tmp=l1;
    		else if(dep[l2]>=dep[l1]&&dep[l2]>=dep[l3])
    			tmp=l2;
    		else
    			tmp=l3;
    //		printf("tmp:%d
    ",tmp);
    		printf("%d
    ",max(max(dis(tmp,x),dis(tmp,y)),dis(tmp,z))+1);
    //		int ans=0;
    	}
    	return 0;
    }
    

    839B

    Link

    假设四个坐的数量为 (four),两个的为 (two),一个的为 (one)。将所有数都尽量放到 (four) 里,其次是 (two),如果过程中 (four,two) 都不过就输出 ( exttt{NO}),然后将剩下的从大到小排个序;遍历剩下的人,如果剩下了 (3),就优先放到一个 (four) 里,其次是两个 (two),都不行就把它拆成 (1)(2),放到数组的后面;如果剩下 (2),优先放一个 (two),其次一个 (four)(如果选这个,需要 one++),其次是两个 (one),如果都不行就拆成两个 (1) 放到后面;如果剩下 (1),优先选一个 (one),其次一个 (two),其次一个 (four)two++),如果都不行就输出 (mathtt{NO})

    #include<bits/stdc++.h>
    using namespace std;
    inline void read(int &x)
    {
    	x=0;int f=1;
    	char c=getchar();
    	while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    	x*=f;
    }
    int four,two,one;
    const int N=10000;
    int a[N+10];
    int main()
    {
    	int n,k;
    	scanf("%d %d",&n,&k);
    	for(int i=1;i<=k;i++) scanf("%d",&a[i]);
    	four=n;two=n*2;
    	for(int i=1;i<=k;i++)
    	{
    		while(a[i]>=4)
    		{
    			if(four>=1)
    				four--;
    			else if(two>=2)
    				two-=2;
    			else
    			{
    				puts("NO");
    				return 0;
    			}
    			a[i]-=4;
    		}
    	}
    	sort(a+1,a+k+1,greater<int>());
    	// for(int i=1;i<=k;i++)
    	// 	if(a[i]>=4)
    	// 	{
    	// 		puts("NO");
    	// 		return 0;
    	// 	}
    	for(int i=1;i<=k;i++)
    	{
    		if(a[i]==3)
    		{
    			if(four>=1) four--;
    			else if(two>=2) two-=2;
    			else
    			{
    				a[++k]=2;
    				a[++k]=1;
    			}
    		}
    		else if(a[i]==2)
    		{
    			if(two>=1) two--;
    			else if(four>=1)
    			{
    				one++;
    				four--;
    			}
    			else if(one>=2)
    				one-=2;
    			else
    			{
    				a[++k]=1;
    				a[++k]=1;
    			}
    		}
    		else if(a[i]==1)
    		{
    			if(one>=1)
    				one--;
    			else if(two>=1)
    				two--;
    			else if(four>=1)
    			{
    				four--;
    				two++;
    			}
    			else
    			{
    				puts("NO");
    				return 0;
    			}
    		}
    	}
    	puts("YES");
    	return 0;
    }
    

    3.10 & 3.11

    870E

    Link

    特别鸣谢:cjx,glq。

    将纵坐标或横坐标相同的点,将它们相连。此时图中形成若干个连通块,考虑每一个连通块,假设这个连通块有 (c_x) 个不同的 (x) 坐标,(c_y) 个不同的 (y) 坐标,若这个图有环,则给答案的贡献为 (2^{c_x+c_y});反之,若为数则贡献为 (2^{c_x+c_y}-1)

    让我们来感性理解一下下面的式子。考虑一个环,在平面的位置一定形如下面两种情况:

    对于第一种情况,有这样一种方案:

    也就是说,随便找若干条边都能够有一种构造方案使得这些边被选中,也就是 (2^{c_x+c_y}) 种情况。

    第二种情况可以转化成第一种情况,所以略。

    假设新来一个点,形如这样:

    不难得出方案数还是 (2^{c_x+c_y}),只不过 (c_x,c_y) 变了。以上为有环的情况。

    对于无环的情况,一定是下面两种情况:

    不难得出,除了下面所有线都被选中的情况,其他所有情况都能被构造出,也就是 (2^{c_x+c_y}-1) 种情况。

    [mathcal{Q.E.D.} ]

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #include<map>
    using namespace std;
    const int N=1e5+10,M=4e5+10;
    #define int long long
    // void init(int n) {for(int i=1;i<=n;i++) f[i]=i;}
    // int getf(int x) {return f[x]==x?x:f[x]]=getf(f[x]);}
    struct node
    {
    	int x,y,pos;
    	node(){}
    }a[N+10],tmp[N+10];
    bool cmp1(node x,node y) {return x.x<y.x;}
    bool cmp2(node x,node y) {return x.y<y.y;}
    int head[N],ver[M],nxt[M],tot=0;
    void add(int x,int y)
    {
    	ver[++tot]=y;
    	nxt[tot]=head[x];
    	head[x]=tot;
    }
    bool vis[N];
    int cnt=0;
    map<int,bool> xx,yy;
    bool dfs(int x,int fa)
    {
    	bool ans=0;
    	// cnt++;
    	xx[tmp[x].x]=1;
    	yy[tmp[x].y]=1;
    	vis[x]=1;
    	for(int i=head[x];i;i=nxt[i])
    	{
    		int y=ver[i];
    		if(y==fa) continue;
    		if(vis[y]) 
    		{
    			ans=1;
    			continue;
    		}
    		if(dfs(y,x)) ans=1;
    	}
    	return ans;
    }
    const int MOD=1e9+7;
    int qpow(int n)
    {
    	int ans=1,base=2;
    	while(n)
    	{
    		if(n&1) ans*=base,ans%=MOD;
    		base*=base,base%=MOD;
    		// return
    		n>>=1;
    	}
    	return ans;
    }
    signed main()
    {
    	int n;
    	scanf("%lld",&n);
    	for(int i=1;i<=n;i++) 
    	{
    		scanf("%lld %lld",&a[i].x,&a[i].y);
    		a[i].pos=i;
    	}
    	memcpy(tmp,a,sizeof(a));
    	sort(a+1,a+n+1,cmp1);
    	for(int i=1;i<n;i++)
    		if(a[i].x==a[i+1].x)
    		{
    			add(a[i].pos,a[i+1].pos);
    			add(a[i+1].pos,a[i].pos);
    		}
    	sort(a+1,a+n+1,cmp2);
    	for(int i=1;i<n;i++)
    		if(a[i].y==a[i+1].y)
    		{
    			add(a[i].pos,a[i+1].pos);
    			add(a[i+1].pos,a[i].pos);
    		}
    	int ans=1;
    	for(int i=1;i<=n;i++)
    	{
    		if(!vis[i])
    		{
    			xx.clear();
    			yy.clear();
    			// cnt=0;
    			bool flag=dfs(i,0);
    			cnt=(int)xx.size()+(int)yy.size();
    			// printf("x+y:%lld
    ",(int)xx.size()+(int)yy.size());
    			if(flag)
    				ans*=qpow(cnt);
    			else
    				ans*=qpow(cnt)-1;
    			ans%=MOD;
    		}
    	}
    	printf("%lld",ans);
    	return 0;
    }
    

    962F

    Link

    用到了点双的一个性质:每个点双内必有一个能涵盖点双里所有点的简单环。Tarjan 算法求一下所有的点双,判断一下每个点双是否有且仅有一个简单环,也就是是否满足 (|V|=|E|)。如果满足这个条件,那么就把所有的边加到答案里即可。

    一个实现的细节:如果你存点双的方式是保存它们的点的话,那么暴力判断最坏情况下大概是 (mathcal O(nm)) 的,所以可以在 Tarjan 的时候直接存边,这样求出来的点双就是若干个边的形式。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #include<set>
    using namespace std;
    const int N=1e6+10,M=2e6+10;
    int head[N],ver[M],nxt[M],tot=1;
    void add(int x,int y)
    {
    	ver[++tot]=y;
    	nxt[tot]=head[x];
    	head[x]=tot;
    }
    int st[N],low[N],cnt=0,num=0,dfn[N],rt,top,st1[N],top1;
    bool cut[N];
    vector<int> dcc[N];
    int col[N];
    int c[N];
    set<int> v[N],e[N];
    void tarjan(int x,int fa)
    {
    	dfn[x]=low[x]=++num;
    	int c=0;
    	for(int i=head[x];i;i=nxt[i])
    	{
    		int y=ver[i];
    		if(!dfn[y])
    		{
    			st[++top]=x;
    			st[++top]=y;
    			st[++top]=i/2;
    			// printf("i/2:%d
    ",i/2);
    			tarjan(y,x);
    			low[x]=min(low[x],low[y]);
    			if(low[y]>=dfn[x])
    			{
    				c++;
    				if(x!=rt||c>1) cut[x]=1;
    				cnt++;
    				while(1)
    				{
    					int pos=st[top--],V=st[top--],U=st[top--];
    					v[cnt].insert(U);v[cnt].insert(V);
    					e[cnt].insert(pos);
    					if(x==U && V==y) break;
    				}
    			}
    		}
    		else if(dfn[y]<dfn[x] && y!=fa)
    		{
    			low[x]=min(low[x],dfn[y]);
    			st[++top]=x;
    			st[++top]=y;
    			st[++top]=i/2;
    		}
    	}
    }
    int main()
    {
    	int n,m;
    	scanf("%d %d",&n,&m);
    	for(int i=1;i<=m;i++)
    	{
    		int x,y;
    		scanf("%d %d",&x,&y);
    		add(x,y);
    		add(y,x);
    	}
    	for(int i=1;i<=n;i++)
    		if(!dfn[i])
    		{
    			rt=i;
    			tarjan(i,0);
    		}
    	int ans=0;
    	set<int> Ans;
    	for(int i=1;i<=cnt;i++)
    		if(e[i].size()==v[i].size())
    			for(set<int>::iterator it=e[i].begin();it!=e[i].end();it++)
    				Ans.insert(*it);	
    	printf("%d
    ",(int)Ans.size());
    	for(set<int>::iterator it=Ans.begin();it!=Ans.end();it++)
    		printf("%d ",*it);
    	return 0;
    }
    

    3.12

    819B

    Link

    (k)(k+1),相当于把最后一个数挪到第一个数。记录几个值,( exttt{ftot,fcnt,ztot,zcnt}),分别记录 (p_i-ile 0)(i-p_i) 的和,(p_i-ile 0) 的数量,(p_i-i>0)(p_i-i) 的和,(p_i-i>0) 的数量。把所有可能的 (k) 枚举一遍,如果没有 (i<p_i) 变为 (ige p_i) 或者后者变为前者的情况,每次必定会有 ( exttt{ztot-=zcnt, ftot+=fcnt})。对于变化的情况,有下面两种:

    • (i<p_i) 变为 (ige p_i),可以预处理出这些点变化的时间;
    • (ige p_i) 变为 (i<p_i),这种情况发生当且仅当后面的挪到前面,特判一下即可。
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    #define int long long
    const int N=2e6;
    int a[N+10],c[N+10];
    signed main()
    {
    	int n;
    	scanf("%lld",&n);
    	int ftot=0,fcnt=0,ztot=0,zcnt=0;
    	for(int i=1;i<=n;i++) 
    	{
    		scanf("%lld",&a[i]);//a[i]-i
    		if(a[i]<=i)
    		{
    			fcnt++;
    			ftot+=i-a[i];
    		}
    		else
    		{
    			zcnt++;
    			ztot+=a[i]-i;
    			c[a[i]-i]++;
    		}
    	}
    	int ans=ztot+ftot,k=0;
    	for(int i=1;i<n;i++)
    	{
    		int x=a[n-i+1];
    		ztot-=zcnt;
    		ftot+=fcnt;
    		zcnt-=c[i];
    		fcnt+=c[i];
    
    		ftot-=n-x;
             ftot++;
    		fcnt--;
    		if(x==1) fcnt++;
    		else
    		{
    			c[x+(i-1)]++;
    			ztot+=(x-1);
    			zcnt++;
    		}
    
    		if(ftot+ztot<ans)
    		{
    			ans=ftot+ztot;
    			k=i;
    		}
    	}
    	printf("%lld %lld",ans,k);
    	return 0;
    }
    

    3.13

    959D

    Link

    由于是字典序,(i) 越小,(x_i) 也要尽量的小,但不能小过 (Y) 的字典序。由于要保证互质,所以可以开一个 multiset,维护在选了若干个 (x_i) 后还能选哪些数,在这里面二分(lower_bound())搜这一轮选那个数即可;每当选了一个数,就将其分解质因数并用类似埃氏筛的方式将这些质因数的倍数在 multiset 中全都 erase 掉。

    分析一下复杂度。假设 (x_i) 的值域最大是 (m)(实现中开成 (2 imes 10^6) 可过),那么所有质数进行一次筛法时间复杂度的上界是 (mathcal O(m/1+m/2+cdots+m/m)=mathcal O(mln m))(由于质数数量只有 (pi(n)approx n/ln n) 那么多,所以远到不了这个上界),加上 erase 操作是 (mathcal O(mln mlog n)),分解质因数的时间复杂度是 (mathcal O(nsqrt{max{a_i}})),所以说总的时间复杂度是 (mathcal O(mln mlog n+nsqrt{max{a_i}}))

    #include<bits/stdc++.h>
    using namespace std;
    const int N=2e6;
    multiset<int> s;
    bool f[N+10];
    int a[N+10],n;
    void bj(int x)
    {//biao ji
    	for(int i=1;i*x<=N;i++)
    	{
    		f[i*x]=1;
    		if(s.find(i*x)!=s.end()) s.erase(i*x);
    	}
    }
    void divide(int n)
    {
    	for(int i=2;i*i<=n;i++)
    	{
    		if(n%i==0)
    		{
    			bj(i);
    			while(n%i==0) n/=i;
    		}
    	}
    	if(n>1)
    	{
    		bj(n);
    	}
    }
    int main()
    {
    	scanf("%d",&n);
    	for(int i=2;i<=N;i++) s.insert(i);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	f[1]=1;
    	divide(a[1]);
    	bool flag=1;
    	printf("%d ",a[1]);
    	for(int i=2;i<=n;i++)
    	{
    		if(flag)
    		{
    			multiset<int>::iterator it=s.lower_bound(a[i]);
    			printf("%d ",*it);
    			divide(*it);
    			if(*it>a[i]) flag=0;
    		}
    		else
    		{
    			multiset<int>::iterator it=s.lower_bound(2);
    			printf("%d ",*it);
    			divide(*it);
    		}
    		// divide(j);
    		// printf("%d ",j);
    	}
    	return 0;
    }
    

    949C

    Link

    (x) 调整导致 (y) 必须调整,那么就在它们两个之间连一条边。形式化地,对于每个 ((x_i,y_i)),若 (u_{x_i}+1equiv u_{y_i}pmod h),则连一条 (x_i o y_i) 的边;若 (u_{y_i}+1equiv u_{x_i}pmod h),则连一条 (y_i o x_i) 的边。强连通分量缩个点,大小最小的、在新图中没有出度的强连通分量中的点就是答案。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int N=1e5,M=2e5;
    int head[N+10],ver[M+10],nxt[M+10],tot=0;
    void add(int x,int y)
    {
    	ver[++tot]=y;
    	nxt[tot]=head[x];
    	head[x]=tot;
    }
    int low[N+10],dfn[N+10],num=0,sz[N+10],cnt=0,col[N+10];
    int st[N+10],top=0;
    bool vis[N+10];
    void tarjan(int x)
    {
    	low[x]=dfn[x]=++num;
    	vis[x]=1;
    	st[++top]=x;
    	for(int i=head[x];i;i=nxt[i])
    	{
    		int y=ver[i];
    		if(!dfn[y])
    		{
    			tarjan(y);
    			low[x]=min(low[x],low[y]);
    		}
    		else if(vis[y])
    			low[x]=min(low[x],dfn[y]);
    	}
    	if(dfn[x]==low[x])
    	{
    		int y=0;++cnt;
    		do
    		{
    			vis[y]=0;
    			y=st[top--];
    			col[y]=cnt;
    			sz[cnt]++;
    		}while(x!=y);
    	}
    }
    int u[N+10],out[N+10];
    int main()
    {
    	int n,m,h;
    	scanf("%d%d%d",&n,&m,&h);
    	for(int i=1;i<=n;i++) scanf("%d",&u[i]);
    	for(int i=1;i<=m;i++)
    	{
    		int x,y;
    		scanf("%d%d",&x,&y);
    		if((u[x]+1)%h==u[y]%h) add(x,y);
    		if((u[y]+1)%h==u[x]%h) add(y,x);
    	}
    	for(int i=1;i<=n;i++) 
    		if(!dfn[i])
    			tarjan(i);
    	for(int x=1;x<=n;x++)
    		for(int i=head[x];i;i=nxt[i])
    		{
    			int y=ver[i];
    			if(col[x]!=col[y])
    				out[col[x]]++;
    		}
    	int ans=0x7fffffff,f=0;
    	for(int i=1;i<=n;i++)
    		if(!out[col[i]] && sz[col[i]]<=ans)
    		{
    			ans=sz[col[i]];
    			f=col[i];
    		}
    	printf("%d
    ",ans);
    	for(int i=1;i<=n;i++)
    		if(col[i]==f)
    			printf("%d ",i);
    	return 0;
    }
    

    722D

    Link

    把所有数扔到一个 set 中,执行若干次操作,每次找出 set 中的最大值 (t)*--s.end()),不断地 (tgetslfloor t/2 floor) 直到 set 中没有与其相等的元素(while(s.count(t)&&t) t/=2),若 (t ot= 0),就将 set 中原本的 (t) 修改为现在的 (t);反之,说明最大值无法更小,退出循环。最后 set 中的元素就是答案。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<set>
    using namespace std;
    int main()
    {
    	set<int> s;
    	int n;
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)
    	{
    		int x;
    		scanf("%d",&x);
    		s.insert(x);
    	}
    	while(1)
    	{
    		int t=*--s.end(),tt=t;
    		while(s.count(t)&&t) t/=2;
    		if(t==0) break;
    		s.erase(tt);s.insert(t);
    	}
    	for(set<int>::iterator it=s.begin();it!=s.end();it++)
    		printf("%d ",*it);
    	return 0;
    }
    

    3.14

    1066F

    Link

    最优答案一定是类似这样的路径:

    QQ图片20210314173816.png

    (f(i,0/1)) 表示走完 (max{x,y}=i)(i) 是离散化之后的值)的线,在左上((p_{i,0}))/右下((p_{i,1}))结束的最小答案,则有转移:

    [f(i,0)=min{f(i-1,0)+operatorname{dis}(p_{i-1,0},p_{i,1}),f(i-1,1)+operatorname{dis}(p_{i-1,1},p_{i,1})}\ f(i,1)=min{f(i-1,0)+operatorname{dis}(p_{i-1,0},p_{i,0}),f(i-1,0)+operatorname{dis}(p_{i-1,1},p_{i,0})} ]

    其中 (operatorname{dis}(x,y)) 表示 (x,y) 的曼哈顿距离。令 (M=max{m_i}),则 (min{f(M,0),f(M,1)}) 即为最终答案。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<vector> 
    using namespace std;
    #define int long long
    const int N=2e5;
    int x[N+10],y[N+10],t[N*2+10];
    struct node
    {
    	int x,y;
    	node(int xx,int yy){x=xx;y=yy;}
    	node(){}
    	bool operator<(const node &rhs)const{return y==rhs.y?x<rhs.x:y>rhs.y;}
    };
    vector<node> p[N+10];
    int c[N+10],f[N+10][2];
    int dis(node a,node b) {return abs(a.x-b.x)+abs(a.y-b.y);}
    signed main()
    {
    	int n,m=0;
    	scanf("%lld",&n);
    	for(int i=1;i<=n;i++) 
    	{
    		scanf("%lld %lld",&x[i],&y[i]);
    		t[++m]=max(x[i],y[i]);
    	}
    	sort(t+1,t+m+1);
    	m=unique(t+1,t+m+1)-t-1;
    	for(int i=1;i<=n;i++)
    		p[lower_bound(t+1,t+m+1,max(x[i],y[i]))-t].push_back(node(x[i],y[i]));
    	p[0].push_back(node(0,0));
    	for(int i=1;i<=n;i++)
    		sort(p[i].begin(),p[i].end());
    	for(int i=1;i<=m;i++)
    	{
    		for(int j=1;(unsigned)j<p[i].size();j++)
    			c[i]+=dis(p[i][j],p[i][j-1]);
    	} 
    	for(int i=1;i<=m;i++)
    	{
    		//0:begin, 1:end
    		vector<node>::iterator it0=p[i-1].begin(),it1=--p[i-1].end();
    		vector<node>::iterator now0=p[i].begin(),now1=--p[i].end();
    		f[i][0]=min(f[i-1][0]+dis(*it0,*now1)+c[i],f[i-1][1]+dis(*it1,*now1)+c[i]);
    		f[i][1]=min(f[i-1][0]+dis(*it0,*now0)+c[i],f[i-1][1]+dis(*it1,*now0)+c[i]);
    	}
    	printf("%lld
    ",min(f[m][0],f[m][1]));
    }	
    // Hawking forever!
    

    1481E

    Link

    正难则反。令 (f(i)) 表示 (isim n) 最多不动多少个,(l_j,r_j) 分别表示颜色 (j) 的最左坐标、最右坐标,(c_{j}) 表示颜色 (j)(isim n) 中的数量,则:

    [f(i)=maxegin{cases}f(i+1)\c_j, & i ot=l_{a_i}\c_j + f(r_{a_i}+1), &i=l_{a_i}end{cases} ]

    关于为什么当 (i ot= l_{a_i}) 时的转移不能是 (c_j+f(r_{a_i}+1)),可以看下面的样例:

    input:
    1 1 2 1 3
    
    output:
    2
    
    correct f(i):
    3 3 1 1 1
    
    incorrect f(i):
    4 4 3 2 1
    

    如果转移了 (c_j+f(r_{a_i}+1)),相当于规定了 (f(r_{a_i}+1)) 这些数不能再移动,而这显然是不对的。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int N=5e5;
    int a[N+10],l[N+10],r[N+10],f[N+10],cnt[N+10];
    void  ckmx(int &x,int y) {x=max(x,y);}
    int main()
    {
    	int n;
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%d",&a[i]);
    		if(!l[a[i]]) l[a[i]]=i;
    		r[a[i]]=i;
    	}
    	for(int i=n;i;i--)
    	{
    		cnt[a[i]]++;
    		f[i]=f[i+1]; 
    		if(l[a[i]]==i) ckmx(f[i],cnt[a[i]]+f[r[a[i]]+1]);
    		else ckmx(f[i],cnt[a[i]]);
    	}
    //	for(int i=1;i<=n;i++) printf("%d ",f[i]);
    	printf("%d",n-f[1]);
    	return 0;
    }
    

    3.15

    1027E

    Link

    确定第一行和第一列后整个图形就确定下来了。对它们进行 dp,令 (f(i,j)) 表示前 (i) 行/列,最长同色串 (le j) 的数量,那么有转移 (f(i,j)gets f(i-k',min{i-k',j}))。利用前缀和的思想,确定前 (n) 行,最长同色串长度为 (j) 的数量 (g(j)=f(n,j)-f(n,j-1)),则答案为 (prodlimits_{i=1}^n prodlimits_{j=1}^n [ijle k]g(i)g(j))

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int N=500;
    int f[N+10][N+10],dp[N+10];
    int main()
    {
    	int n,K;
    	scanf("%d%d",&n,&K);
    	f[0][0]=1;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=i;j++)
    		{
    			for(int k=1;k<=j;k++)
    			{
    				f[i][j]+=f[i-k][min(i-k,j)];
    				f[i][j]%=998244353;
    			}
    		}
    	}
    	for(int i=1;i<=n;i++)
    	{
    		dp[i]=f[n][i]-f[n][i-1];
    		dp[i]+=998244353;dp[i]%=998244353;
    	}
    	int ans=0;
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++)
    			if(i*j<K)
    			{
    				ans+=(long long)dp[i]*dp[j]%998244353;
    				ans%=998244353;
    			}
    	printf("%d",ans*2%998244353);
    	return 0;
    }
    

    1029E

    Link

    预处理出深度 (d_i),插到 priority_queue 里,每次取出深度最大的点, 并将其父亲及父亲的周围节点标记。

    感性理解一下:每次能够取出来的点一定是叶子节点或者其后代已经处理完毕,此时这个点如果想要变合法有两种方式:将自己与根连边或将父亲与根连边。前者最多只能影响到两个点(自己,父亲),而后者则会影响到与自己同一父亲的其他节点,所以从贪心的角度来讲,每次选深度最大的节点的父亲不会使答案更劣。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    #define int long long
    const int N=1e5;
    int n,x,y,z;
    //+1:x, -1:y, +1-1:z
    int a[N+10];
    int calc(int h)
    {
        int sum1=0,sum2=0;
        for(int i=1;i<=n;i++)
        {
            if(a[i]>=h) sum1+=a[i]-h;
            else sum2+=h-a[i];
        }
        if(sum1>sum2) return min(sum1*y+sum2*x,(sum1-sum2)*y+sum2*z);
        else return min(sum1*y+sum2*x,(sum2-sum1)*x+sum1*z);
    }
    int bin3()
    {
        int l=0,r=1e9;
        int ans=min(calc(l),calc(r));
        while(l<=r)
        {
            int lmid=(l+r)/2,rmid=(lmid+r)/2;
            int fl=calc(lmid),fr=calc(rmid);
            if(fl>fr) l=lmid+1;
            else r=rmid-1;
            ans=min(ans,min(fl,fr));
        }
        return ans;
    }
    signed main()
    {
        scanf("%lld%lld%lld%lld",&n,&x,&y,&z);
        z=min(z,x+y);
        for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
        printf("%lld",bin3());
        return 0;
    }
    

    1355E

    Link

    先把 (M) 变成 (min{M,A+B})。考虑给一个 (H),代价是多少。令 (c_1=sumlimits_{i=1}^n [h_ige H](h_i-H),;c_2=sumlimits_{i=1}^n [h_i<H](H-h_i)),若 (c_1>c_2),答案为 (min{Bc_1+Ac_2,B(c_1-c_2)+Mc_2});若 (c_1le c_2),答案为 (min{Bc_1+Ac_2,A(c_2-c_1)+Mc_1})。这个东西是一个单谷的,所以可以三分 (H)

    提供一个整数三分的模板(https://www.cnblogs.com/--560/p/5242883.html):

    ll bin3(int l,int r)
    {
        if(l>r) return -INF;
        ll res=max(f(l),f(r));
        while(l<=r){
            int m=(l+r)>>1,mm=(m+r)>>1;
            ll fm=f(m),fmm=f(mm);
            if(fm<=fmm) l=m+1;
            else r=mm-1;
            res=max(res,max(fm,fmm));
        }
        return res;
    }
    
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    #define int long long
    const int N=1e5;
    int n,x,y,z;
    //+1:x, -1:y, +1-1:z
    int a[N+10];
    int calc(int h)
    {
        int sum1=0,sum2=0;
        for(int i=1;i<=n;i++)
        {
            if(a[i]>=h) sum1+=a[i]-h;
            else sum2+=h-a[i];
        }
        if(sum1>sum2) return min(sum1*y+sum2*x,(sum1-sum2)*y+sum2*z);
        else return min(sum1*y+sum2*x,(sum2-sum1)*x+sum1*z);
    }
    int bin3()
    {
        int l=0,r=1e9;
        int ans=min(calc(l),calc(r));
        while(l<=r)
        {
            int lmid=(l+r)/2,rmid=(lmid+r)/2;
            int fl=calc(lmid),fr=calc(rmid);
            if(fl>fr) l=lmid+1;
            else r=rmid-1;
            ans=min(ans,min(fl,fr));
        }
        return ans;
    }
    signed main()
    {
        scanf("%lld%lld%lld%lld",&n,&x,&y,&z);
        z=min(z,x+y);
        for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
        printf("%lld",bin3());
        return 0;
    }
    

    3.16

    63D

    Link

    答案一定是 YES,因为可以这样填:

    (b<d)(bmod 2=0) 则起点在 ((1,a+c))(bmod 2=1) 则起点在 ((1,1));若 (bge d)(dmod 2=0) 则起点在 ((1,1))(dmod 2=1) 则起点在 ((1,a+c))。从左到右、从右到左反复填即可,对于起点的讨论避免了死胡同的情况。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    #define mp make_pair
    #define fi first
    #define se second 
    int x[27];
    char m[200][200];
    int main()
    {
    	int a,b,c,d,n;
    	scanf("%d %d %d %d %d",&a,&b,&c,&d,&n);
    	for(int i=1;i<=max(b,d);i++)
    		for(int j=1;j<=a+c;j++)
    			m[i][j]='.';
    	for(int i=1;i<=n;i++) scanf("%d",&x[i]);
    	pair<int,int> s;
    	bool right=0;
    	if(b>d)
    	{
    		if(d%2==0) s=mp(1,1),right=1;
    		else s=mp(1,a+c),right=0;
    	}
    	else
    	{
    		if(b%2==0) s=mp(1,a+c),right=0;
    		else s=mp(1,1),right=1;
    	}
    	int l=1,r=a+c;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=x[i];j++)
    		{
    			m[s.fi][s.se]=i+'a'-1;
    			if(right) s.se++;
    			else s.se--;
    			if(s.se>r)
    			{
    				s.fi++;
    				if(s.fi>min(b,d))
    				{
    					if(b>d) r=a;
    					else l=a+1;
    				}
    				s.se=r;
    				right^=1;
    			}
    			else if(s.se<l)
    			{
    				s.fi++;
    				if(s.fi>min(b,d))
    				{
    					if(b>d) r=a;
    					else l=a+1;
    				}
    				s.se=l;
    				right^=1;
    			}
    		}
    	}
    	puts("YES");
    	for(int i=1;i<=max(b,d);i++)
    	{
    		for(int j=1;j<=a+c;j++)
    			putchar(m[i][j]);
    		putchar('
    '); 
    	}
    	return 0;
    }
    

    67D

    Link

    记值 (i)(y) 中的位置记为 (p_i),同时令 (c_i=p_{a_i})(c) 的最长下降子序列就是答案。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    inline void read(int &x)
    {
        x=0;;int f=1;
        char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
        while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
        x*=f;
    }
    const int N=1e6+10;
    struct seg
    {
        int l,r,val;
        seg(){}
    }t[N<<2];
    void build(int p,int l,int r)
    {
        t[p].l=l;t[p].r=r;
        t[p].val=0;
        if(l==r) return;
        int mid=(l+r)/2;
        build(p*2,l,mid);
        build(p*2+1,mid+1,r);
    }
    int query(int p,int l,int r)
    {
        if(l<=t[p].l&&t[p].r<=r) return t[p].val;
        int ans=0,mid=(t[p].l+t[p].r)/2;
        if(l<=mid) ans=max(ans,query(p*2,l,r));
        if(r>mid) ans=max(ans,query(p*2+1,l,r));
        return ans;
    }
    void modify(int p,int l,int d)
    {
    	if(t[p].l==t[p].r)
    	{
    		t[p].val=d;
    		return;
    	}
    	int mid=(t[p].l+t[p].r)/2;
    	if(l<=mid) modify(p*2,l,d);
    	else modify(p*2+1,l,d);
    	t[p].val=max(t[p*2].val,t[p*2+1].val);
    }
    int a[N],b[N],pos[N],f[N];
    int main()
    {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++) scanf("%d",&a[i]);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&b[i]);
            pos[b[i]]=i;
        }
        for(int i=1;i<=n;i++) a[i]=pos[a[i]];
        build(1,1,n);
        int ans=0;
        for(int i=1;i<=n;i++)
        {
        	f[i]=max(1,query(1,a[i]+1,n)+1);
        	modify(1,a[i],f[i]);
        	ans=max(ans,f[i]);
    	}
    	printf("%d",ans);
    	return 0;
    }
    

    69D

    Link

    dfs。bool dfs(x, y) 表示搜到了 ((x,y)),执棋者是否能赢。若 dfs(x + dx[i], y + dy[i]) 有一个返回的是 (0),则 dfs(x, y) 直接返回 (1)。记忆化一下,复杂度是 (mathcal O(d^2n))

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    int vis[1010][1010],dx[30],dy[30],n,d;
    int dfs(int x,int y)
    {
        if(~vis[x][y]) return vis[x][y];
        if((x-200)*(x-200)+(y-200)*(y-200)>d*d) return vis[x][y]=1;
        for(int i=1;i<=n;i++)
            if(dfs(x+dx[i],y+dy[i])==0)
                return vis[x][y]=1;
        return vis[x][y]=0;
    }
    int main()
    {
        memset(vis,-1,sizeof(vis));
        int x,y;
        scanf("%d %d %d %d",&x,&y,&n,&d);
        for(int i=1;i<=n;i++)
            scanf("%d %d",&dx[i],&dy[i]);
        if(dfs(x+200,y+200)==1)
            printf("Anton");
        else printf("Dasha");
        return 0;
    }
    

    3.18

    914D

    Link

    一个区间能够满足条件,当且仅当这个区间的 gcd 是 (x) 的倍数或者存在一个分界点使得分界点左边、右边的 gcd 都是 (d) 的倍数。线段树维护区间 gcd,树上二分计算这个分界点即可。时间复杂度 (mathcal O((n+m) log^2 n))

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    int gcd(int x,int y) {return y==0?x:gcd(y,x%y);}
    const int N=5e5+10;
    int a[N];
    struct seg
    {int l,r,g;}t[N<<2];
    void build(int p,int l,int r)
    {
    	t[p].l=l;t[p].r=r;
    	if(l==r)
    	{
    		t[p].g=a[l];
    		return;
    	}
    	int mid=(l+r)/2;
    	build(p*2,l,mid);
    	build(p*2+1,mid+1,r);
    	t[p].g=gcd(t[p*2].g,t[p*2+1].g);
    }
    void modify(int p,int x,int d)
    {
    	if(t[p].l==t[p].r)
    	{
    		t[p].g=d;
    		return;
    	}
    	int mid=(t[p].l+t[p].r)/2;
    	if(x<=mid) modify(p*2,x,d);
    	else modify(p*2+1,x,d);
    	t[p].g=gcd(t[p*2].g,t[p*2+1].g);
    }
    int d,pos=0;
    void Query(int p)
    {
    	if(t[p].l==t[p].r)
    	{
    		if(!pos) pos=t[p].l;
    		return;
    	}
    	if(t[p*2].g%d) Query(p*2);
    	else Query(p*2+1);
    }
    void query1(int p,int l,int r)
    {
    	if(l<=t[p].l&&t[p].r<=r) 
    	{
    		if(t[p].g%d) Query(p);
    		return;
    	}
    	int mid=(t[p].l+t[p].r)/2;
    	if(l<=mid) query1(p*2,l,r);
    	if(r>mid) query1(p*2+1,l,r);
    }
    int query(int p,int l,int r)
    {
    	if(l<=t[p].l&&t[p].r<=r) return t[p].g;
    	int ans=0,mid=(t[p].l+t[p].r)/2;
    	if(l<=mid) ans=gcd(query(p*2,l,r),ans);
    	if(r>mid) ans=gcd(query(p*2+1,l,r),ans);
    	return ans;
    }
    int main()
    {
    	int n;
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	build(1,1,n);
    	int m;
    	scanf("%d",&m);
    	for(int i=1;i<=m;i++)
    	{
    		// printf("i=%d
    ",i);
    		int p;
    		scanf("%d",&p);
    		if(p==2)
    		{
    			int x,d;
    			scanf("%d%d",&x,&d);
    			modify(1,x,d);
    			a[x]=d;
    		}
    		else
    		{
    			int l,r;
    			scanf("%d%d%d",&l,&r,&d);
    			if(l==r)
    			{
    				puts("YES");
    				continue;
    			}
    			if(query(1,l,r)%d==0)
    			{
    				puts("YES");
    				continue;
    			}
    			pos=0;
    			query1(1,l,r);
    			if(pos==l)
    			{
    				if(query(1,l+1,r)%d) puts("NO");
    				else puts("YES");
    				continue;
    			}
    			if(pos==r)
    			{
    				if(query(1,l,r-1)%d) puts("NO");
    				else puts("YES");
    				continue;
    			}
    			if(query(1,l,pos-1)%d==0 && query(1,pos+1,r)%d==0) puts("YES");
    			else puts("NO");
    		}
    	}
    	return 0;
    }
    

    935D

    Link

    (f(i,0/1)) 表示考虑前 (i) 位,前 (i) 位里 (A=B)(A>B) 的数量,暴力转移即可。最终答案为 (dfrac{f(n,1)}{m^k}),其中 (k) 表示 (A,B)(0) 的数量。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    #define int long long
    const int N=1e5+10,MOD=1e9+7;
    int a[N],b[N],f[N][2];
    int qpow(int x,int n)
    {
    	x%=MOD;
    	int ans=1;
    	while(n)
    	{
    		if(n&1) ans=ans*x%MOD;
    		x=x*x%MOD;
    		n>>=1;
    	}
    	return ans;
    }
    signed main()
    {
    	int n,m;
    	scanf("%lld%lld",&n,&m);
    	int cnt=0;
    	for(int i=1;i<=n;i++) 
    	{
    		scanf("%lld",&a[i]);
    		cnt+=(a[i]==0);
    	}
    	for(int i=1;i<=n;i++) 
    	{
    		scanf("%lld",&b[i]);
    		cnt+=(b[i]==0);
    	}
    	f[0][0]=1;
    	for(int i=1;i<=n;i++)
    	{
    		if(a[i]&&b[i])
    		{
    			if(a[i]==b[i])
    			{
    				f[i][0]=f[i-1][0];
    				f[i][1]=f[i-1][1];
    			}
    			else if(a[i]>b[i])
    				f[i][1]=f[i-1][0]+f[i-1][1];
    			else if(a[i]<b[i])
    				f[i][1]=f[i-1][1];
    			f[i][0]%=MOD;
    			f[i][1]%=MOD;
    		}
    		else if(a[i]&&!b[i])
    		{
    			f[i][1]=f[i-1][1]*m%MOD+f[i-1][0]*(a[i]-1)%MOD;
    			f[i][1]%=MOD;
    			f[i][0]=f[i-1][0];
    		}
    		else if(!a[i]&&b[i])
    		{
    			f[i][1]=f[i-1][1]*m%MOD+f[i-1][0]*(m-b[i])%MOD;
    			f[i][1]%=MOD;
    			f[i][0]=f[i-1][0];
    		}
    		else if(!a[i]&&!b[i])
    		{
    			f[i][0]=f[i-1][0]*m%MOD;
    			f[i][0]%=MOD;
    			f[i][1]=f[i-1][0]*(m-1)%MOD*m%MOD*500000004ll%MOD
    						+ f[i-1][1]*m%MOD*m%MOD;
    		}
    	}
    	// puts("f[i][0]:");
    	// for(int i=1;i<=n;i++) printf("%lld ",f[i][0]);
    	// puts("");
    	int di=qpow(qpow(m,cnt),MOD-2);
    	printf("%lld",f[n][1]*di%MOD);
    	return 0;
    }
    

    930C

    Link

    考虑什么情况下会存在一个点使得这个点被所有区间覆盖,可以得出,若令 (f(x)) 表示 (x) 的覆盖次数,那么当 (x) 单峰或单调的时候存在一个点使得这个点被所有区间覆盖,于是这题就变成了合唱队列加强版,线段树 (mathcal O(n log n)) 直接转移即可。

    
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    using namespace std;
    const int N=100000+10;
    struct segmenttree
    {
    	struct seg
    	{
    		int l,r,val;
    		seg(){l=r=val=0;}
    	}t[N<<2];
    	void build(int p,int l,int r)
    	{
    		t[p].l=l;t[p].r=r;
    		if(l==r) return;
    		int mid=(l+r)/2;
    		build(p*2,l,mid);
    		build(p*2+1,mid+1,r);
    	}
    	void modify(int p,int x,int d)
    	{
    		if(t[p].l==t[p].r)
    		{
    			t[p].val=d;
    			return;
    		}
    		int mid=(t[p].l+t[p].r)/2;
    		if(x<=mid) modify(p*2,x,d);
    		else modify(p*2+1,x,d);
    		t[p].val=max(t[p*2].val,t[p*2+1].val);
    	}
    	int query(int p,int l,int r)
    	{
    		if(l<=t[p].l && t[p].r<=r) return t[p].val;
    		int mid=(t[p].l+t[p].r)/2,ans=0;
    		if(l<=mid) ans=max(ans,query(p*2,l,r));
    		if(r>mid) ans=max(ans,query(p*2+1,l,r));
    		return ans;
    	}
    }a,b;
    int c[N],d[N];
    int f1[N],f2[N];
    int main()
    {
    	int n,m;
    	scanf("%d %d",&n,&m);
    	for(int i=1;i<=n;i++)
    	{
    		int l,r;
    		scanf("%d %d",&l,&r);
    		c[l]++;
    		c[r+1]--;
    	}
    	int sum=0;
    	for(int i=1;i<=m;i++)
    	{
    		sum+=c[i];
    		// printf("%d ",sum);
    		d[i]=sum;
    	}
    	a.build(1,0,n);
    	b.build(1,0,n);
    	for(int i=1;i<=m;i++)
    	{
    		f1[i]=a.query(1,0,d[i])+1;
    		a.modify(1,d[i],f1[i]);
    		// printf("%d ",f1[i]);
    	}
    	for(int i=m;i;i--)
    	{
    		f2[i]=b.query(1,0,d[i])+1;
    		b.modify(1,d[i],f2[i]);
    		// printf("%d ",f2[i]);
    	}
    	int ans=0;
    	for(int i=0;i<=m;i++) ans=max(ans,f1[i]+f2[i+1]);
    	printf("%d",ans);
    	return 0;
    }
    

    3.19 ~ 3.31

    博客园维护,所以写到了本地,下载链接

  • 相关阅读:
    Linux操作系统基础(完结)
    单台主机上DB2 10.5和arcgis 10.4 空间数据库配置
    java连接oracle数据库使用SERVICE NAME、SID以及TNSName不同写法
    arcgis desktop 地理编码服务发布
    利用arcgis发布综合又详细的地理定位服务
    centos 安装arcgis server 10.1
    spark源代码
    bootstrap.memory_lock: true导致Elasticsearch启动失败问题
    安装jdk后出现bash: ./java: /lib/ld-linux.so.2: bad ELF interpreter: 没有那个文件或目录
    pyspark采用python3开发
  • 原文地址:https://www.cnblogs.com/juruo-zzt/p/14486553.html
Copyright © 2011-2022 走看看