NOIP2012提高组题解

\(D1T1\) \(Vigenère\)密码 \((OK)\)

\(D1T2\) 国王游戏 \((OK)\)

\(D1T3\) 开车旅行

\(D2T1\) 同余方程 \((OK)\)

\(D2T2\) 借教室 \((OK)\)

\(D2T3\) 疫情控制

因为最近时间比较紧,所以紫题一律跳过,等最后一个星期不联考的时候再回来填坑.

\(D1T1\)就字符串模拟题,刚开始看错题了,题目给的是密钥和密文,要求明文.注意区分一下大小写.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
char s1[1005],s2[1005];
char b[26]={'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'};
char a[26]={'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'};
int main(){
	scanf("%s%s",s1+1,s2+1);
	int k=strlen(s1+1),n=strlen(s2+1);
	for(int i=1;i<=k;++i)//把密钥补全
		for(int j=1;i+j*k<=n;++j)s1[i+j*k]=s1[i];		
	for(int i=1;i<=n;++i){
		if(s2[i]>='A'&&s2[i]<='Z'){
			if(s1[i]>='A'&&s1[i]<='Z'){
				int x=s2[i]-'A'-(s1[i]-'A');
				x+=26;printf("%c",a[x%26]);
			}
			if(s1[i]>='a'&&s1[i]<='z'){
				int x=s2[i]-'A'-(s1[i]-'a');
				x+=26;printf("%c",a[x%26]);
			}
		}
		if(s2[i]>='a'&&s2[i]<='z'){
			if(s1[i]>='A'&&s1[i]<='Z'){
				int x=s2[i]-'a'-(s1[i]-'A');
				x+=26;printf("%c",b[x%26]);
			}
			if(s1[i]>='a'&&s1[i]<='z'){
				int x=s2[i]-'a'-(s1[i]-'a');
				x+=26;printf("%c",b[x%26]);
			}
		}
	}
	printf("\n");return 0;
}

\(D1T2\)恩,很经典的贪心题了,见过好多次了.本题结论是通过贪心证明方法\(---\)邻项微扰法证的.因为后面\(40\)分是高精,所以我懒得再写代码了,就口胡一下证明过程吧.

假设最优方案中有两位大臣\(i\)\(i+1\),它们之前\((1\)~\(i-1)\)位大臣左手乘积为\(sum\).那么此时产生的贡献是\(max(\frac{sum}{b_i},\frac{sum*a_i}{b_{i+1}})\),如果交换这两位大臣,那么产生的贡献是\(max(\frac{sum}{b_{i+1}},\frac{sum*a_{i+1}}{b_{i}})\).为了下文表示方便,按照出场顺序我们把这四个整数依次记为\(k_1,k_2,k_3,k_4\).

首先根据式子显然有\(k_1<k_4,k_2>k_3\),因为前面那种是最优方案,所以\(max(k_1,k_2)<max(k_3,k_4)\),那么可以推得\(k_2<k_4.\)

\(\frac{sum*a_i}{b_{i+1}}<\frac{sum*a_{i+1}}{b_{i}}\)

所以有\(sum*a_i*b_i<sum*a_{i+1}*b_{i+1}\),同时约掉\(sum\)

\(a_i*b_i<a_{i+1}*b_{i+1}\)

所以本题的贪心策略就是按照左手右手的乘积从小到大排序.

\(D1T3\)咕咕咕.

\(D2T1\)最近考过两次了,扩欧的模板题.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline ll read(){
    ll x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
ll a,b,x,y;
inline void exgcd(ll a,ll b,ll &x,ll &y){
	if(!b){x=1;y=0;return;}
	exgcd(b,a%b,x,y);
	ll z=x;x=y;y=z-y*(a/b);
}
int main(){
	a=read();b=read();exgcd(a,b,x,y);
	printf("%lld\n",(x%b+b)%b);
    return 0;
}

\(D2T2\),刚开始看到这道题其实是看错题了,以为是要求出所有不能满足要求的请求.然后看这些操作其实就是线段树的区间查询最小值和区间增加(负数),所以直接写的线段树模板,当然在看清题目后发现还是可以用线段树写啊.题目只要求第一个不能被满足的请求.

然后发现这样的话,还可以二分答案\(mid\),这时只要检查是否有教室的前缀和大于该教室给定的那个数即可.因为如果第\(mid\)个请求时,有一个教室的前缀和大于该教室给定的那个数,那么第\(mid+1\)个请求时,那个教室的前缀和只会更大.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=1e6+5;
int n,m,ask_min;
int tot[N],minn[N<<2],add[N<<2];
inline void build(int p,int l,int r){
	if(l==r){minn[p]=tot[l];add[p]=0;return;}
	int mid=(l+r)>>1;
	build(p<<1,l,mid);build(p<<1|1,mid+1,r);
	minn[p]=min(minn[p<<1],minn[p<<1|1]);
}
inline void pushdown(int p,int l,int r,int mid){
	if(!add[p])return;
	add[p<<1]+=add[p];add[p<<1|1]+=add[p];
	minn[p<<1]+=add[p];minn[p<<1|1]+=add[p];
	add[p]=0;
}
inline void ask(int p,int l,int r,int ql,int qr){
	if(ql<=l&&qr>=r){ask_min=min(ask_min,minn[p]);return;}
	int mid=(l+r)>>1;pushdown(p,l,r,mid);
	if(ql<=mid)ask(p<<1,l,mid,ql,qr);
	if(qr>mid)ask(p<<1|1,mid+1,r,ql,qr);
}
inline void change(int p,int l,int r,int ql,int qr,int val){
	if(ql<=l&&qr>=r){add[p]+=val;minn[p]+=val;return;}
	int mid=(l+r)>>1;pushdown(p,l,r,mid);
	if(ql<=mid)change(p<<1,l,mid,ql,qr,val);
	if(qr>mid)change(p<<1|1,mid+1,r,ql,qr,val);
	minn[p]=min(minn[p<<1],minn[p<<1|1]);
}
int main(){
	n=read();m=read();
	for(int i=1;i<=n;++i)tot[i]=read();
	build(1,1,n);
	for(int i=1;i<=m;++i){
		int z=read(),x=read(),y=read();
		ask_min=1<<30;ask(1,1,n,x,y);
		if(ask_min<z){printf("-1\n%d\n",i);return 0;}
		change(1,1,n,x,y,-z);
	}
	puts("0");
    return 0;
}

二分答案当然全方位(时空复杂度,码量)吊打线段树:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
    return x*o;
}
const int N=1e6+5;
int n,m,x[N],y[N],z[N],tot[N],sum[N];
inline bool check(int mid){
	for(int i=1;i<=n;++i)sum[i]=0;
	for(int i=1;i<=mid;++i)sum[x[i]]+=z[i],sum[y[i]+1]-=z[i];	
	int now=0;
	for(int i=1;i<=n;++i){
		now+=sum[i];
		if(now>tot[i])return false;
	}
	return true;
}
int main(){
	n=read();m=read();
	for(int i=1;i<=n;++i)tot[i]=read();
	for(int i=1;i<=m;++i)z[i]=read(),x[i]=read(),y[i]=read();
	int l=0,r=n,mid,ans;
	while(l<=r){
		mid=(l+r)>>1;
		if(check(mid))ans=mid,l=mid+1;
		else r=mid-1;
	}
	if(ans==n){puts("0");return 0;}
	printf("-1\n%d\n",ans+1);
    return 0;
}

posted on 2019-11-04 17:24  PPXppx  阅读(188)  评论(0编辑  收藏  举报