2020 Multi-University Training Contest 3

1、Parentheses Matching

题意 :给定一个字符串里面包含’(’、’)’、’*’, 给定一个操作可以将’ *‘替换成’(‘或者’)’,问我们能够是否可以将原序列变成一个合法的括号序列,若可以则输出最小的长度且满足字典序最小,反之输出“No Solution!”

思路:
贪心的去考虑,首先一个很重要的性质,假设一个字符串是一个合法序列那么满足:
假设’('为+1,‘)’为-1;
(1)、任意位置前缀和大于0,
(2)、最后的和等于0

如何构造最小长度且字典序最小的序列:
很显然如果原序列已经是个合法序列了那么不需要加了直接输出就行,
反之我们要设法构造,即让’(‘尽可能的在左边,因为要求字典序最小,当遇到’*‘的时候如何判断它
是否要被替换成’(’ ,我们可以维护一个数组mn[i]:代表从i-n中最少需要多少个 ‘)’,这样从前向后构造
“(”只需要判断之前出现过的’ *"+mn[i+1]是否小于0即可

接着就是从后向前构造’)’
用op代表之前记录的’*‘改变成’(’的数量,当从后向前构造的时候 只要遇见 “ * ”就可以将其变成“)”。

最后再根据性质判断下时候符合合法序列即可:
代码

#include

using namespace std;

const int N=1e5+10;

int sum[N];
int mn[N];
char s[N];

int main()
{
     
	int T;
	cin>>T;
	while(T--) {
     
		int n;
		scanf("%s",s+1);
		n=strlen(s+1);
		//先判断是否满足括号序列 
		//任意位置sum>0,'('+1,')'-1 
		bool ok=true;
		sum[0]=0;
		for(int i=1;i<=n;i++) {
     
			sum[i]=sum[i-1];
			if(s[i]=='(') sum[i]++;
			else if(s[i]==')')sum[i]--;
		} 
		
		//预处理最小的需要多少')'序列
		mn[n+1]=n+1;
		for(int i=n;i>=1;i--) {
     
			mn[i]=min(mn[i+1],sum[i]);
		} 
		//从前向后构造'('
		int op=0;
		for(int i=1;i<=n;i++) {
     
			if(s[i]=='*') {
     
				if(mn[i+1]+op<0) {
     
					s[i]='(';
					op++;
				}
			}
			if(sum[i]+op<0) ok=false;
		}
		if(!ok) 
		{
     
			puts("No solution!");
			continue;
		}
		//从后向前构造')'
		op+=sum[n];
		for(int i=n;i>=1;i--) {
     
			if(s[i]=='*'&&op>0) {
     
				op--;
				s[i]=')';
			}
		}
		int cnt=0;
		for(int i=1;i<=n;i++) {
     
			if(s[i]=='(') cnt++;
			else if(s[i]==')') cnt--;
			if(cnt<0) ok=false;
		}  
		if(cnt!=0) ok=false ;
		if(!ok) puts("No solution!");
		else {
     
			for(int i=1;i<=n;i++) {
     
				if(s[i]!='*') cout<<s[i];
			}
			puts("");
		}
	}
	return 0;
}

2、Tokitsukaze and Multiple

题意:
好混淆的题意。有n堆数字,可以选择合并相邻两堆,判断合并过程中为p的倍数的最大个数为多少个。
还是比较喜欢DP的解法:
定义F[i]:所有以i为结尾的产生的p的倍数的最大个数;
状态转移:考虑那些状态可以转移到i,刚开始想的那肯定是余数相加以后等于p的呀,后来想想:哪些状态可以影响到F[i],维护一个模p意义下的前缀和,记录它上一个出现的这样前缀和的位置即可,它上一个一定也会被它上上一个更新,总之就递推了22333,我还以为是区间DP,,,

注: ind[0]=0!!

代码:

#include

using namespace std;

const int N=1e5+10;

int a[N];
int n,p;
int f[N];
int sum[N];
int ind[N];

int main()
{
     
	int T;
	cin>>T;
	while(T--) {
     
		scanf("%d%d",&n,&p);
		memset(f,0,sizeof f);
		memset(ind,-1,sizeof ind);
		for(int i=1;i<=n;i++)
		{
     
			scanf("%d",&a[i]);
			a[i]%=p;
			sum[i]=(sum[i-1]+a[i])%p;
		}
		ind[0]=0;
		for(int i=1;i<=n;i++) {
     
			f[i]=f[i-1];
			if(ind[sum[i]]!=-1) 
				f[i]=max(f[i],f[ind[sum[i]]]+1);
			ind[sum[i]]=i;
		}
		cout<<f[n]<<'\n';
	}
	return 0;
} 

3、Little W and Contest

题意:
有两个ACMer集合 标记为1和2 ,一个ACM队至少要有两个2,然后接下来会告诉你n-1个关系,一个ACM队内要保证任意两名队员互相不认识,问每次输入一个关系前有多少种选择方案,答案对(1e9+7)取余。

两个比较坑的地方:一个取余(x+mod)%mod
一个是思维上坑的地方,本题很显然是并查集,维护每个连通块内部1的数量和2的数量即可,在两个点u,v合并的时候考虑什么样的方式会对总答案有影响:有四种:
从u中选一个2 v中选一个1 剩下一个2在剩下的人中随便选
从u中选一个2 v中选一个2 剩下一个1在剩下的人中随便选
从u中选一个1 v中选一个2剩下的一个2在剩下的人中随便选
从u中选一个2 v中选一个2 剩下的一个2在剩下的人中随便选
注:这里剩下的人中是指除了u,v两个集合外的,不能从两个里面选,因为要保证自身的合法性,不在同一个连通块内部。
代码:

#include
#define x first 
#define y second

using namespace std;

const  int N=1e5+10,mod=1e9+7;

typedef long long LL;
typedef pair<LL,LL>PII;

LL fact[N],infact[N];
int ind[N],vis[N];
PII size[N];
int f[N];
LL fans[N];

int find(int x)
{
     
    if(x!=f[x]) f[x]=find(f[x]);
    return f[x];
}

LL qmi(LL a, LL k, LL p)
{
     
    LL res = 1;
    while (k)
    {
     
        if (k & 1) res = (LL)res * a % p;
        a = (LL)a * a % p;
        k >>= 1;
    }
    return res;
}

void init()
{
     
    fact[0] = infact[0] = 1;
    for (int i = 1; i < N; i ++ )
    {
     
        fact[i] = (LL)fact[i - 1] * i % mod;
        infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;
    }
}

LL C(LL a,LL b) {
     
    if(a<b) return 0;
    else return (LL)fact[a] * infact[b] % mod * infact[a - b] % mod;
}

int main()
{
     
    int T;
    scanf("%d",&T);
    init();
    //cout<<(LL)C(5000,3)<<'\n';
    while(T--) {
     
        int cnt=0;
        scanf("%d",&cnt);
        
        LL n=0,m=0;
        //n 为2的数量 m为1 的数量  
        for(int i=1;i<=cnt;i++)
        {
     
            scanf("%d",&ind[i]);
            if(ind[i]==2) n++;
            else m++;
        }
        //size 中x代表1的个数 y代表2的个数 
        for(int i=1;i<=cnt;i++) {
     
            f[i]=i;
            size[i].x=(ind[i]==1?1:0);
            size[i].y=(ind[i]==2?1:0);
        }
        int p=0,q=0;
        LL ans=(LL)C(n,2)*C(m,1)%mod+C(n,3)%mod;
        ans%=mod;
        int s=ans;
        fans[0]=ans;
        for(int i=1;i<cnt+1;i++) {
     
            int u,v;
            if(i<cnt) scanf("%d%d",&u,&v);
            int fu=find(f[u]);
            int fv=find(f[v]);
            if(fu==fv) continue;
            
            //p1为u中1的个数 q1为u中2的个数
            //p2为v中1的个数 q2为v中2的个数 
            LL p1=size[fu].x,q1=size[fu].y;
            LL p2=size[fv].x,q2=size[fv].y;
            
            //x=2 y=2 1
            //if(m>=p1+p2)
            ans=((ans-1LL*q1*(m-p1-p2)%mod*q2)%mod+mod)%mod;
            
            //x=2 y=1 2
            //if(n>=q1+q2)
            ans=((ans-1LL*q1*(n-q1-q2)%mod*p2)%mod+mod)%mod;
            
            //x=2 y=2 2
            //if(n>=q1+q2)
            ans=((ans-1LL*q1*(n-q1-q2)%mod*q2)%mod+mod)%mod;
            
            //x=1 y=2 2
            //if(ans>=(p1*(q2*(n-q1-q2)%mod))) 
            ans=((ans-1LL*p1*(q2*(n-q1-q2)%mod))%mod+mod)%mod;
            
            size[fv].x=(size[fv].x+size[fu].x);
            size[fv].y=(size[fv].y+size[fu].y); 
            f[fu]=fv;
            fans[i]=ans;
        //    fans[i]=max(0LL,ans);
        }
        for(int i=0;i<cnt;i++)    
            printf("%lld\n",fans[i]);
    }
    return 0;
}

你可能感兴趣的:(2020,Training,Cont,Acm之旅___思维,Acm之旅___HDU刷题分类)