good problems-6

1.E - Addition and Multiplication 2
显然选的数越多,那么最后结果的长度就越大,值就越大
设minn为C中最小的花费
那么最长长度就是\(\lfloor \frac{n}{minn} \rfloor\)
那么长度确定了,我们要答案最大肯定是选的数的位置越大,答案就越大
那么我们贪心从位置大到小,在保证最后答案长度不变的情况下,能选就选

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
using namespace std;
const int maxn=1e6+10101;
const int inf=2147483647;
const int MOD=998244353;
const double eps=1e-9;
ll read(){
	ll x=0,f=1;char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int n,c[11]; 
int main() {
	n=read();int minn=inf;
	for(int i=1;i<=9;i++)c[i]=read(),minn=min(minn,c[i]);
	int now=n;
	for(int i=9;i>=1;i--){
		int t=now;
		for(int j=1;j*c[i]<=now;j++){
			if(now/minn!=(now-j*c[i])/minn+j)break;
			t-=c[i];
			printf("%d",i);
		}
		now=t;
	}
	return 0;
}
2.[F - Teleporter Setting](https://atcoder.jp/contests/abc257/tasks/abc257_f) 大致题意就是: 有m条边,n个点 其中m条边包含一些不确定的边,表示形式就是(0,x) 0称为不确定的点(可以代替其他点),x称为特殊点 n个操作,第i个操作将所有不确定的点都改为i,也就是边(0,x)改为(i,x) 问每次操作,1~n的最短距离,到达不了就是-1

题解:
首先将1和n分别跑遍bfs,求1和n分别到其他点的距离a[i],b[i]
再记录1和n分别到所有特殊点的最短距离a0,b0
那么答案就是分类讨论
1.1和n联通,ans=a[n]
2.1到不确定的点k,再到特殊点,再到n, ans=a[k]+1+b0
3.1到特殊点,再到不确定的点k,再到n, ans=a0+1+b[k]
4.1到特殊点,再到不确定点k,再到特殊点,再到n,ans=a0+2+b0

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
using namespace std;
const int maxn=1e6+10101;
const int inf=2147483647;
const int MOD=998244353;
const double eps=1e-9;
ll read(){
	ll x=0,f=1;char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int n,m;
int tot,head[maxn],nx[maxn],to[maxn];
void add(int x,int y){to[++tot]=y;nx[tot]=head[x];head[x]=tot;}
vector<int>sp;
struct BFS{
	int dis[maxn],d0=inf/3+1;
	bool book[maxn];
	void bfs(int x){
		queue<int>q;
		for(int i=1;i<=n;i++)dis[i]=-1;
		q.push(x);book[x]=1;dis[x]=0;
		while(!q.empty()){
			int u=q.front();q.pop();
			for(int i=head[u];i;i=nx[i]){
				int v=to[i];if(book[v])continue;
				dis[v]=dis[u]+1;book[v]=1;q.push(v);
			}
		}
		for(auto i:sp){
			if(dis[i]==-1)continue;
			d0=min(d0,dis[i]);
		}
		return ;
	}
}a,b;
int main() {
	n=read();m=read();
	for(int i=1;i<=m;i++){
		int x=read(),y=read();
		if(x>y)swap(x,y);
		if(x==0)sp.push_back(y);
		else add(x,y),add(y,x);
	}
	a.bfs(1);b.bfs(n);
	for(int i=1;i<=n;i++){
		int ans=inf;
		if(a.dis[n]!=-1)ans=min(ans,a.dis[n]);
		if(b.dis[i]!=-1)ans=min(ans,a.d0+1+b.dis[i]);
		if(a.dis[i]!=-1)ans=min(ans,b.d0+1+a.dis[i]);
		ans=min(ans,a.d0+b.d0+2);
		if(ans>inf/3)ans=-1;
		printf("%d ",ans);
	}
	return 0;
}

3.C - Tree Queries
交互题
大致题意:有一个树,你可以询问2n次,问2点的距离,最后让你求1到2的距离,不能询问1到2距离
不难想到求1到所有点的距离和2到所有点的距离
\(d_i=dis_{1->i}+dis_{2->i}\)
那么最小的\(D=d_i\)可能就是答案
但对于D=3要特殊考虑,因为可能答案是1
类似如下图

不难看出D=1和3是有区别的
D=3一般如下所示

会有且只有2个点,也就是两个不同\(d_i\)都是3,所以小于或大于2个点的答案就是1
而图一也有2个点,但距离是1,那么就要在询问一下那两个点的距离

点击查看代码
#include <bits/stdc++.h>
#define ll long long
#define pa pair<int,int>
using namespace std;
const int maxn=1e6+10101;
const int inf=2147483647;
const int MOD=998244353;
const double eps=1e-9;
ll read(){
	ll x=0,f=1;char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int dis[101][111];
int query(int x,int y){
	printf("? %d %d\n",x,y);
	fflush(stdout);
	x=read();
	return x;
}
int main() {
	int n=read();
	for(int i=3;i<=n;i++){
		dis[1][i]=query(1,i);
		dis[2][i]=query(2,i);
	}
	int minn=inf;
	for(int i=3;i<=n;i++)minn=min(minn,dis[1][i]+dis[2][i]);
	vector<int>u;
	if(minn==3){
		for(int i=3;i<=n;i++)if(dis[1][i]+dis[2][i]==3)u.push_back(i);
		if(u.size()!=2)minn=1;
		else if(query(u[0],u[1])!=1)minn=1;
	}
	printf("! %d",minn);
	fflush(stdout);
	return 0;
}

4.C. The Third Problem
题意:给定一个排列a,求有多少个排列能够与a相似。相似定义2个排列的任何区间的mex要相同
题解:
设p[i]为i所在排列的位置
对于与a类似的排列b,首先可以判断p[0]和p[1]在b中是与a中相同的
设L=p[0],R=p[1](假设p[0]<p[1],两者大小不影响结果)
那么对与p[2]来说有2种可能

  1. L<p[2]<R
    那么2可以放在(L,R)中任何位置,ans*=(r-l+1)-k(k为[L,R]中不可变的个数)

2.非第一种情况,2的位置不可改变(可以找例子试试)
那么我们调整L=min(L,p[2]),R=max(R,p[2])
对于3~n-1重复上述操作

点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long 
#define pa pair<ll,int>
using namespace std;
const int maxn=2e5+101;
const int MOD=1e9+7;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;

int read(){
    int x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
} 

int a[maxn],p[maxn];
int main(){
    int t=read();
    while(t--){
        int n=read();
        for(int i=1;i<=n;i++)a[i]=read(),p[a[i]]=i;
        ll ans=1,l=min(p[1],p[0]),r=max(p[0],p[1]);
        if(n==1){puts("1");continue;}
        for(int i=2;i<n;i++){
            if(l<p[i] && p[i]<r){
                ans*=((r-l+1ll)-i);
                ans%=MOD;
            }
            else {
                l=min((ll)p[i],l);r=max((ll)p[i],r);
            }
        }
        printf("%lld\n",(ans%MOD+MOD)%MOD);
    }
    return 0;
}

5.D. Almost Triple Deletions



我们可以预处理出del[l][r]表示区间[l,r]是否可以被删除

注意一些细节
当dp[j]=0的时候不能转移,因为[1,j]无法变为全为\(a_j\)的数

点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long 
#define pa pair<ll,int>
using namespace std;
const int maxn=5e3+101;
const int MOD=1e9+7;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;

int read(){
    int x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
} 

int a[maxn],del[maxn][maxn],book[maxn];
int main(){
    int t=read();
    while(t--){
        int n=read();
        for(int i=1;i<=n;i++)a[i]=read();
        for(int l=1;l<=n;l++){
            memset(book,0,sizeof(int)*(n+2));
            int maxx=0;
            for(int r=l;r<=n;r++){
                del[l][r]=0;
                book[a[r]]++;
                maxx=max(book[a[r]],maxx);
                if((r-l+1)&1)continue;
                if(maxx<=(r-l+1)/2)del[l][r]=1;
            }
        }
        vector<int>dp(n+1);int ans=0;
        dp[1]=1;
        for(int i=1;i<=n;i++){
            if(del[1][i-1])dp[i]=1;
            if(a[i]==a[i-1] && dp[i-1])dp[i]=max(dp[i],dp[i-1]+1);
            for(int j=1;j<i-1;j++){
                if(a[j]!=a[i] || !del[j+1][i-1] || !dp[j])continue;
                dp[i]=max(dp[i],dp[j]+1);
            }
            if(del[i+1][n] || i==n)ans=max(ans,dp[i]);
        }
        printf("%d\n",ans);
    }
    return 0;
}

6.A. The Third Three Number Problem

7.G - Triangle
题意是:给一个邻接矩阵,找有多少个3元环(a->b->c->a)
不难想到枚举a,b,c来找,复杂度是\(O(N^3)\),显然会超时
那么如何通过枚举a和b快速找到c
可以用一个技巧bitset,把每一个点i的邻接矩阵存到bitset里B[i]
那么枚举a和b,通过(B[a]&B[b]).count()来算出有多少个c

点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long 
#define pa pair<ll,int>
using namespace std;
const int maxn=5e3+101;
const int MOD=1e9+7;
const int inf=2147483647;
const double pi=acos(-1);
const double eps=1e-12;
ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}

int n;
bitset<3001>b[3001];
int main() {
    n=read();
    for(int i=1;i<=n;i++){
        string a;cin>>a;
        for(int j=1;j<=n;j++){
            if(a[j-1]=='1')b[i][j]=1;
            else b[i][j]=0;
        }
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            if(b[i][j]==1)ans+=(b[i]&b[j]).count();
        }
    }
    printf("%lld",ans/3);
    return 0;
}

8.Cut the Sequence
题意:
给定一个长度为N的序列A,要求把该序列分成若干段,在满足每段中所有数的和不超过 M的前提下,让每段中所有数的最大值之和最小。
题解:
不难写出状态转移方程,设\(dp_i\)为前i个数分段的价值最小值
\(dp_i=min \{ dp_j+max( a_{j+1},···,a_i ) \},\sum_{k=j+1}^i a_j \leq m\)
这个转移是\(O(n^2)\)
考虑如何优化
对于每个i的j的范围是可以预处理出来的,特定范围的最大值也可以用单调队列算出来
但如何快速只找到特定j,使得\(dp_j+max( a_{j+1},···,a_i )\)最小
首先由于a数组全大于0,则dp数组非单调递减,那么对于某个特定范围的最大值\(max(a_{j+1},···,a_i)\),一定找到能够满足条件的最靠前的\(dp_j\)来搭配
注意可行区间内的一个单调队列中的所有元素都可能成为最优的转移
我们用一个平衡树来维护可行区间内的\(min \{ dp_j+max(a_{j+1},···,a_i) \}\)
具体看代码

点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long 
#define pa pair<ll,int>
using namespace std;
const int maxn=2e6+101;
const int MOD=1e9+7;
const int inf=2147483647;
const double eps=1e-12;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int n,a[maxn],c[maxn],q[maxn];
ll m,f[maxn];
multiset<ll>s;
int main() {
    n=read();m=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
        if(a[i]>m){puts("-1");return 0;}
    }
    ll sum=0;
    for(int i=1,j=0;i<=n;i++){
        sum+=a[i];
        while(sum>m)sum-=a[j+1],j++;
        c[i]=j;        
        //c数组维护i所能向前延伸的最大位置-1
    }
    int l=1,r=0;
    for(int i=1;i<=n;i++){
        while(l<=r && q[l]<=c[i])s.erase(f[q[l]]+a[q[l+1]]),l++;
        while(l<=r && a[q[r]]<=a[i])s.erase(f[q[r-1]]+a[q[r]]),r--;
        if(l<=r)s.insert(f[q[r]]+a[i]);
        q[++r]=i;
        f[i]=f[c[i]]+a[q[l]];
        if(!s.empty())f[i]=min(f[i],*s.begin());
    }
    printf("%lld",f[n]);
    return 0;
}

9.Freezing
设每个人的状态为\(b_i\)
普通\(dp_{i,j}\)表示前i个人选队列,且最后一个人的状态是j的方案数,状态转移不难写出,可以转化为一维\(dp_j\)
\(O(n2^m)\)显然超时
注意题目数据有暗示

神奇的dp
\(dp_{i,j}\)表示当前队列最后一个人状态的前8位是i,且后8位是与j无交集的方案数
对于每个\(b_i\),答案累加

更新dp数组

可以感性理解下

点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#define ll long long 
using namespace std;
const int maxn=2e5+101;
const ll MOD=998244353;
const int inf=2147483647;
ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int n,m;
ll dp[1<<8][1<<8];
int b[maxn];
int main(){
    n=read();m=read();
    for(int i=1;i<=n;i++){
        int now=0;
        string a;cin>>a;
        for(int j=0;j<m;j++)if(a[j]=='o')now+=(1<<j);
        b[i]=now;
    }
    ll ans=0;
    for(int i=1;i<=n;i++){
        int x=b[i]>>8,y=b[i]&255;//x前8位,y后八位
        ll z=1;             //i单独一队
        for(int j=0;j<(1<<8);j++){
            if(!(x&j))(z+=dp[j][y])%=MOD;
        }
        ans+=z;ans%=MOD;
        for(int j=0;j<(1<<8);j++){
            if(!(y&j))(dp[x][j]+=z)%=MOD;
        }
    }
    printf("%lld",(ans%MOD+MOD)%MOD);
    return 0;
}

10.G2. Passable Paths (hard version)
题意:给一颗n个点的树,q次询问,每次询问给若干个点,问是否这些点是否在一条链上
题解:
我们将链分成两种情况
1.从根节点开始的链

这种情况可以用dfs序来做
将询问的每个点的dfs序排序,然后从大到小判断每个点是否在后一个点的子树内

2.另一种是有拐点的链

那么如何判断一条带“拐点”的路径呢?我们希望找到经过这些点的路径的起点和终点。
那么我们可以指定深度最大的那个点是起点。
然后我们如何找到终点呢?我们发现,如果是终点到”拐点G“的路径上的点x,与st的lca都是x
所以我们需要找\(lca(x,st)\not = x\) 的深度最大的点作为我们的终点。
image
G=lca(st,ed)
我们可以枚举询问中的点x
如果它在st到G的路径中,则有\(lca(st,x)=x,lca(x,ed)=G\)
如果它在ed到G的路径中,则有\(lca(st,x)=G,lca(x,ed)=x\)
如果二者都不满足,则说明有一个点不在路径上。

点击查看代码
#include<functional>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<complex>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<deque>
#include<stack>
#include<map>
#include<set>
#define ll long long 
#define pa pair<int,int>
using namespace std;
const int maxn=2e6+101;
const int MOD=998244353;
const int inf=2147483647;
const double eps=1e-9;

ll read(){
    ll x=0,f=1;char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
    return x*f;
}
int n,q,k,a[maxn];
int tot,head[maxn],nx[maxn],to[maxn];
void add(int x,int y){to[++tot]=y;nx[tot]=head[x];head[x]=tot;}
int dfn[maxn],dep[maxn],cnt,R[maxn],f[maxn][21];
void dfs(int x,int fa){
    dfn[x]=++cnt;
    for(int i=1;i<=20;i++)f[x][i]=f[f[x][i-1]][i-1];
    for(int i=head[x];i;i=nx[i]){
        int v=to[i];if(v==fa)continue;
        dep[v]=dep[x]+1;f[v][0]=x;dfs(v,x);
    }
    R[x]=cnt;
    return ;
}
bool isp(int u,int v){
    return dfn[u]<=dfn[v] && R[v]<=R[u];
}
bool ischain(){
    sort(a+1,a+k+1,[](int i,int j){return dfn[i]>dfn[j];});
    int l=dfn[a[1]];
    for(int i=2;i<=k;i++){
        if(R[a[i]]>=l)continue;
        return 0;
    }
    return 1;
}

int lca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    for(int i=20;i>=0;i--){
        if(dep[f[x][i]]>=dep[y])x=f[x][i];
        //若dep[y=1]=0,x可能最后变成0
    }
    if(x==y)return x;
    for(int i=20;i>=0;i--){
        if(f[x][i]!=f[y][i]){
            x=f[x][i];
            y=f[y][i];
        }
    }
    return f[x][0];
}
int main(){
    n=read();
    for(int i=1;i<n;i++){
        int x=read(),y=read();
        add(x,y);add(y,x);
    }
  //根结点1的深度一定要设为1,不然dep[1]=0,会导致求lca出现问题
    dep[1]=1;dfs(1,0);
    q=read();
    while(q--){
        k=read();
        for(int i=1;i<=k;i++)a[i]=read();
        if(ischain() || k<=2){puts("YES");continue;}
        sort(a+1,a+k+1,[](int i,int j){return dep[i]>dep[j];});
        int st=a[1],ed;
        for(int i=2;i<=k;i++){
            if(lca(a[i],st)!=a[i]){ed=a[i];break;}
        }
        int G=lca(st,ed);bool fa=true;
        for(int i=1;i<=k;i++){
            int x1=lca(st,a[i]),x2=lca(ed,a[i]);
            if(x1==a[i] && x2==G)continue;
            if(x1==G && x2==a[i])continue;
            fa=false;break;
        }
        if(!fa)puts("NO");
        else puts("YES");
    }
    return 0;
}
posted @ 2022-06-29 14:32  I_N_V  阅读(19)  评论(0编辑  收藏  举报