省选测试16

总结

三道题拿的都是暴力分,和预估的基本一样

A. 环

分析

因为 \(n\) 不一定是质数,所以要用 \(exgcd\) 求逆元

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define rg register
const int maxn=1e3+5;
int t,n,k,l;
char s[maxn];
int exgcd(rg int aa,rg int bb,rg int &xx,rg int &yy){
	if(bb==0){
		xx=1,yy=0;
		return aa;
	}
	rg int nans=exgcd(bb,aa%bb,xx,yy);
	rg int tt=xx;
	xx=yy;
	yy=tt-aa/bb*yy;
	return nans;
}
int gcd(rg int aa,rg int bb){
	return bb==0?aa:gcd(bb,aa%bb);
}
int getny(rg int now){
	rg int x,y;
	exgcd(now,n,x,y);
	x=(x%n+n)%n;
	return x;
}
inline int mulmod(rg long long now1,rg int now2){
	return now1*=now2,now1>=n?now1%n:now1;
}
int main(){
	scanf("%d",&t);
	while(t--){
		memset(s,0,sizeof(s));
		scanf("%d%d%d",&n,&k,&l);
		if(gcd(n,k)!=1){
			printf("NO\n");
			continue;
		}
		printf("YES\n");
		for(rg int i=0;i<n;i++) s[i]='0';
		for(rg int i=0;i<k;i++) s[i]='0';
		for(rg int i=0;i<k;i++) s[mulmod(i,getny(k))]='1';
		for(rg int i=0;i<l;i++){
			s[mulmod(i,getny(k))]='0';
			s[(mulmod(i,getny(k))+1)%n]='1';
			printf("%s\n",s);
		}
	}
	return 0;
}

B.DNA序列

分析

对于最优解是按照 \(1 \sim n\) 的顺序连接的数据,我们考虑贪心

枚举每一个字符串有多长的前缀在最优解中,如果比当前答案更优,就更新答案

枚举的时候应该倒序枚举,而不应该正序枚举

比如

AAA
B

如果我们正着去贪心,那么对于第一个字符串,我们会选择 \(A\)

对于第二个字符串,我们会选择 \(B\)

最终的答案就是 \(AB\)

显然是不对的

但是如果倒着去贪心

对于第一个字符串,我们肯定在前面接得越多越好

所以会得到正确的答案 \(AAAB\)

这样做复杂度是 \(n^4\) 的,\(n\) 比较小,可以通过

接下来的问题就在于没有特殊性质的时候如何排序

考虑把一个字符串 \(s\) 分成两个部分

前半部分是一个最短的前缀 \(x\),满足 \(x^{+\infty}\)

后半部分是该字符串剩下的部分 \(y\)

比如 \(AACG\)

我们就可以把它写成 \(A^{+\infty}+CG\) 的形式

这样写会有一个问题,只包含一个字母的字符串无法处理

因此要在每一个字符串后面加一个字典序较大的字母,比如 \(Z\)

那么 \(A\) 就可以写成 \(A^{+\infty} + Z\) 的形式

前半部分肯定要按照字典序从小到大排,后半部分则需要按照字典序从大到小排

因为在前面的字符都相同的情况下

肯定要把下一个字典序较小的放在后面

前缀重复正无穷大不好处理

可以在前缀 \(x\) 的后面也加一个字典序较大的字母

这样排序的时候就能够把我们想要的排在前面

手模几组样例就明白了

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define rg register
const int maxn=55,maxm=1e4+5;
int n,a[maxn][maxn],ans[maxm],len[maxn],anstp,sta[maxm],tp,sum;
char s[maxn];
struct jie{
	std::string x,z;
	int id;
}b[maxn];
bool cmp(rg jie aa,rg jie bb){
	if(aa.x==bb.x) return aa.z>bb.z;
	return aa.x<bb.x;
}
void updat(){
	for(rg int i=1;i<=tp;i++){
		if(sta[i]>ans[i]) return;
		else if(sta[i]<ans[i]) break;
	}
	for(rg int i=1;i<=tp;i++) ans[i]=sta[i];
	anstp=tp;
	for(rg int i=tp+1;i<=sum;i++) ans[i]=0;
}
void ad(rg int id,rg int nlen){
	for(rg int i=tp;i>=1;i--) sta[i+nlen]=sta[i];
	for(rg int i=1;i<=nlen;i++) sta[i]=a[id][i];
	tp+=nlen;
}
void del(rg int nlen){
	for(rg int i=1;i<=tp-nlen;i++) sta[i]=sta[i+nlen];
	for(rg int i=tp-nlen+1;i<=tp;i++) sta[i]=0;
	tp-=nlen;
}
int main(){
	scanf("%d",&n);
	std::string tmp1,tmp2,tmp3;
	for(rg int i=1;i<=n;i++){
		scanf("%s",s+1);
		len[i]=strlen(s+1);
		sum+=len[i];
		tmp1.clear();
		for(rg int j=1;j<=len[i];j++){
			if(s[j]=='A') a[i][j]=1;
			else if(s[j]=='C') a[i][j]=2;
			else if(s[j]=='G') a[i][j]=3;
			else a[i][j]=4;
			tmp1=tmp1+s[j];
		}
		tmp1=tmp1+'Z';
		b[i].id=i;
		for(rg int j=1;j<=len[i];j++){
			tmp2.clear();
			for(rg int k=1;k<=j;k++) tmp2=tmp2+s[k];
			tmp3=tmp2;
			while(tmp3.length()<=tmp1.length()) tmp3=tmp3+tmp3;
			if(tmp3<=tmp1){
				tmp1.clear();
				rg int ncnt=0;
				for(rg int k=j;k<len[i];k++){
					if(tmp1[k]!=tmp2[k%j]) break;
					ncnt++;
				}
				for(rg int k=j+ncnt/j*j+1;k<=len[i];k++) tmp1=tmp1+s[k];
				tmp2=tmp2+'Z',tmp1=tmp1+'Z';
				b[i].x=tmp2,b[i].z=tmp1;
				break;
			}
		}
	}
	std::sort(b+1,b+1+n,cmp);
	for(rg int i=n;i>=1;i--){
		tp=0;
		for(rg int j=1;j<=anstp;j++) sta[++tp]=ans[j];
		for(rg int j=anstp;j>=1;j--) ans[j+1]=ans[j];
		anstp++;
		ans[1]=a[b[i].id][1];
		for(rg int j=2;j<=len[b[i].id];j++) ad(b[i].id,j),updat(),del(j);
	}
	for(rg int i=1;i<=anstp;i++){
		if(ans[i]==1) printf("A");
		else if(ans[i]==2) printf("C");
		else if(ans[i]==3) printf("G");
		else printf("T");
	}
	printf("\n");
	return 0;
}

C.探寻

分析

把母矿所在处的价值看作无穷大,那么问题就转化成了最少需要多少花费才可以遍历整棵子树

对于收益减花费为正的结点,肯定能选就选

将这些节点按照花费从小到大排序

对于一个收益减花费为正,一个收益减花费为负的节点,肯定要选收益减花费为正的点

因为 \(set\) 中不能有重复的元素,所以对于其它的点,也要随便规定一个优先级

如果当前我们选择的节点能够直接到达

那么我们肯定要选上这个点

否则把它与它的父亲合并

设当前节点为 \(now\),它的父亲为 \(fa\)

分情况讨论

\(cost[now]>val[fa]\)

此时父亲节点的收益不足以到达这个节点,所以要把合并之后节点的花费设为 \(cost[fa]+cost[now]-val[fa]\)

同时收益也要变为 \(val[now]\)

\(cost[now] \leq val[fa]\)

收益加上当前节点的贡献,变为 \(val[fa]+val[now]-cost[now]\)

用并查集维护一下所在的集合就行了

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<set>
#define rg register
inline int read(){
	rg int x=0,fh=1;
	rg char ch=getchar();
	while(ch<'0' || ch>'9'){
		if(ch=='-') fh=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9'){
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*fh;
}
const int maxn=1e6+5;
int fa[maxn],n,f[maxn];
int zhao(rg int xx){
	if(xx==fa[xx]) return xx;
	return fa[xx]=zhao(fa[xx]);
}
struct jie{
	int id;
	long long cost,val;
	jie(){}
	jie(rg int aa,rg long long bb,rg long long cc){
		id=aa,cost=bb,val=cc;
	}
	friend bool operator <(const jie& A,const jie& B){
		if((A.val-A.cost)>=0 && (B.val-B.cost)>=0){
			return A.cost==B.cost?A.id<B.id:A.cost<B.cost;
		} else {
			if(A.val-A.cost==B.val-B.cost) return A.id<B.id;
			return A.val-A.cost>B.val-B.cost;
		}
	}
};
std::set<jie> s;
long long ans,cs,val[maxn],cost[maxn];
int main(){
	n=read();
	rg int aa,bb,cc;
	for(rg int i=2;i<=n;i++){
		aa=read(),bb=read(),cc=read();
		aa++;
		f[i]=aa;
		if(bb!=-1){
			cost[i]=cc,val[i]=bb;
		} else {
			cost[i]=cc,val[i]=0x3f3f3f3f3f3f3f3f;
		}
		s.insert(jie(i,cost[i],val[i]));
	}
	for(rg int i=1;i<=n;i++) fa[i]=i;
	rg jie tmp;
	rg int now;
	while(!s.empty()){
		tmp=*s.begin();
		s.erase(s.begin());
		now=zhao(f[tmp.id]);
		if(now==1){
			if(tmp.cost<=cs){
				cs=cs-tmp.cost+tmp.val;
			} else {
				ans+=tmp.cost-cs;
				cs=tmp.val;
			}
		} else {
			s.erase(s.find(jie(now,cost[now],val[now])));
			if(val[now]<cost[tmp.id]){
				cost[now]=cost[now]+cost[tmp.id]-val[now];
				val[now]=val[tmp.id];
			} else {
				val[now]+=val[tmp.id]-cost[tmp.id];
			}
			s.insert(jie(now,cost[now],val[now]));
		}
		fa[zhao(tmp.id)]=zhao(now);
	}	
	printf("%lld\n",ans);
	return 0;
}
posted @ 2021-02-03 20:04  liuchanglc  阅读(86)  评论(0编辑  收藏  举报