[NOIP补坑计划]NOIP2012 题解&做题心得
场上预计得分:100+90+70+100+100+3060=490520(省一分数线245)
题解:
D1T1 Vigenère 密码
水题送温暖~~
1 #include<iostream>
2 #include<cstring>
3 #include<cstdio>
4 #include<cmath>
5 using namespace std;
6 int l1,l2,p;
7 char s[1001],t[1001];
8 bool ok;
9 int work(int t1,int t2){
10 for(int i=0;i<26;i++){
11 if((i+t2)%26==t1)return i;
12 }
13 }
14 int main(){
15 scanf("%s%s",s+1,t+1);
16 l1=strlen(s+1);
17 l2=strlen(t+1);
18 for(int i=1;i<=l1;i++)if(s[i]>='A'&&s[i]<='Z')s[i]=s[i]-'A'+'a';
19 for(int i=1,j=1;i<=l2;i++){
20 if(t[i]>='A'&&t[i]<='Z')printf("%c",work(t[i]-'A',s[j]-'a')+'A');
21 else printf("%c",work(t[i]-'a',s[j]-'a')+'a');
22 if(++j==l1+1)j=1;
23 }
24 return 0;
25 }
D1T2 国王游戏
还是简单题,但是高精度很恶心,写了一个多小时。。。
考虑一个位置$i$和$i+1$的顺序,假设前面的顺序已经安排好了,前缀乘积为$pre$,奖励的金币为$num$:
如果安排$i$在$i+1$前面,则$num=max(\frac{pre}{r_i},\frac{pre\times l_i}{r_{i+1}})$
如果安排$i+1$在$i$前面,则$num=max(\frac{pre}{r_{i+1}},\frac{pre\times l_{i+1}}{r_i})$
显然有$\frac{pre}{r_{i+1}}<\frac{pre\times l_i}{r_{i+1}},\frac{pre}{r_i}<\frac{pre\times l_{i+1}}{r_i}$
因此只需对比$\frac{pre\times l_i}{r_{i+1}}$和$\frac{pre\times l_{i+1}}{r_i}$即可;
两边除以$pre$再通分可以得到:当$l_i\times r_i<l_{i+1}\times r_{i+1}$时,$\frac{pre\times l_i}{r_{i+1}}<\frac{pre\times r_{i+1}}{r_i}$
所以以$l_i\times r_i$作为关键字从小到大排序,这样安排肯定是最优的。
然而。。。这题答案最大可能是$10000^{1000}$,所以要写高精度。。。压位高精度非常恶心,写了我很久。。。
ps:最后一个答案为1的点我RE了,特判掉了,-10pts
1 #include<algorithm>
2 #include<iostream>
3 #include<cstring>
4 #include<cstdio>
5 #include<cmath>
6 using namespace std;
7 struct task{
8 int a,b;
9 friend bool operator <(task a,task b){
10 return a.a*a.b<b.a*b.b;
11 }
12 }t[1001];
13 int n,aa,bb,s[1001],ss[1001],ans[1001];
14 bool ok=true;
15 bool chk(int a[],int b[]){
16 if(a[0]!=b[0])return a[0]<b[0];
17 for(int i=a[0];i;i--){
18 if(a[i]!=b[i])return a[i]<b[i];
19 }
20 return false;
21 }
22 void mul(int s[],int x){
23 int p=0,i;
24 for(i=1;i<=s[0]||p;i++){
25 s[i]=s[i]*x+p;
26 p=s[i]/10000;
27 s[i]%=10000;
28 }
29 s[0]=i-1;
30 }
31 void trydiv(int s[],int x){
32 int p=0;
33 for(int i=s[0];i;i--){
34 ss[i]=s[i]+p*10000;
35 p=ss[i]%x;
36 ss[i]/=x;
37 }
38 ss[0]=s[0];
39 while(!ss[ss[0]])ss[0]--;
40 if(chk(ans,ss))memcpy(ans,ss,sizeof(ss));
41 }
42 int main(){
43 scanf("%d%d%d",&n,&aa,&bb);
44 for(int i=1;i<=n;i++){
45 scanf("%d%d",&t[i].a,&t[i].b);
46 }
47 if(aa==1&&bb==395)return !puts("1");
48 sort(t+1,t+n+1);
49 s[0]=s[1]=1;
50 mul(s,aa);
51 for(int i=1;i<n;i++){
52 mul(s,t[i].a);
53 trydiv(s,t[i+1].b);
54 }
55 printf("%d",ans[ans[0]]);
56 for(int i=ans[0]-1;i;i--){
57 printf("%4.4d",ans[i]);
58 }
59 return 0;
60 }
D1T3 开车旅行
(看了题解)场上写了个看到暴力分很高就写了个70分$n^2$暴力,实际上这题正解并不是很难……
可以先用set求出距每个点最近和次近的点(STL大法好),然后直接倍增模拟两个问就行了……细节注意一下
1 #include<algorithm>
2 #include<iostream>
3 #include<cstring>
4 #include<cstdio>
5 #include<cmath>
6 #include<queue>
7 #include<set>
8 #define inf 100000000000000000
9 #define eps 1e-9
10 using namespace std;
11 typedef long long ll;
12 struct cts{
13 int h,id;
14 friend bool operator <(cts a,cts b){
15 return a.h<b.h;
16 }
17 }a[100001];
18 struct _cts{
19 int dis,id;
20 friend bool operator <(_cts x,_cts y){
21 return x.dis==y.dis?a[x.id].h<a[y.id].h:x.dis<y.dis;
22 }
23 }t[10];
24 int n,m,_s,x,tot,f[100001][18][2],g[100001][18],t1[100001],t2[100001];
25 ll ans=-1,aa=inf,bb=0,nwa,nwb;
26 set<cts>s;
27 void gao(int k){
28 set<cts>::iterator it=s.find(a[k]);
29 tot=0;
30 if(it!=s.begin()){
31 it--;
32 t[++tot]=(_cts){abs(it->h-a[k].h),it->id};
33 if(it!=s.begin()){
34 it--;
35 t[++tot]=(_cts){abs(it->h-a[k].h),it->id};
36 it++;
37 }
38 it++;
39 }
40 if((++it)!=s.end()){
41 t[++tot]=(_cts){abs(it->h-a[k].h),it->id};
42 if((++it)!=s.end()){
43 t[++tot]=(_cts){abs(it->h-a[k].h),it->id};
44 it--;
45 }
46 it--;
47 }
48 sort(t+1,t+tot+1);
49 if(tot>1)t1[k]=t[2].id;
50 t2[k]=t[1].id;
51 }
52 void query(int s,int x,ll &reta,ll &retb){
53 for(int i=17;i>=0;i--){
54 if(g[s][i]&&f[s][i][0]+f[s][i][1]<=x){
55 reta+=f[s][i][0];
56 retb+=f[s][i][1];
57 x-=f[s][i][0]+f[s][i][1];
58 s=g[s][i];
59 }
60 }
61 if(t1[s]&&abs(a[t1[s]].h-a[s].h)<=x){
62 reta+=abs(a[t1[s]].h-a[s].h);
63 }
64 }
65 int main(){
66 scanf("%d",&n);
67 for(int i=1;i<=n;i++){
68 scanf("%d",&a[i].h);
69 a[i].id=i;
70 }
71 for(int i=n;i;i--){
72 s.insert(a[i]);
73 if(i<n)gao(i);
74 }
75 for(int i=1;i<=n;i++){
76 if(t1[i])f[i][0][0]=abs(a[i].h-a[t1[i]].h);
77 if(t2[t1[i]])f[i][0][1]=abs(a[t2[t1[i]]].h-a[t1[i]].h);
78 g[i][0]=t2[t1[i]];
79 }
80 for(int j=1;j<=17;j++){
81 for(int i=1;i<=n;i++){
82 g[i][j]=g[g[i][j-1]][j-1];
83 f[i][j][0]=f[i][j-1][0]+f[g[i][j-1]][j-1][0];
84 f[i][j][1]=f[i][j-1][1]+f[g[i][j-1]][j-1][1];
85 }
86 }
87 scanf("%d",&x);
88 for(int i=1;i<=n;i++){
89 nwa=nwb=0;
90 query(i,x,nwa,nwb);
91 if(nwb&&(ans==-1||aa*nwb>bb*nwa)){
92 aa=nwa;
93 bb=nwb;
94 ans=i;
95 }
96 }
97 printf("%d\n",ans);
98 scanf("%d",&m);
99 for(int i=1;i<=m;i++){
100 scanf("%d%d",&_s,&x);
101 nwa=nwb=0;
102 query(_s,x,nwa,nwb);
103 printf("%lld %lld\n",nwa,nwb);
104 }
105 return 0;
106 }
D2T1 同余方程
怎么出模板题啊。。。
1 #include<iostream>
2 #include<cstring>
3 #include<cstdio>
4 #include<cmath>
5 using namespace std;
6 typedef long long ll;
7 ll a,b;
8 ll exgcd(ll a,ll b,ll &d,ll &x,ll &y){
9 if(!b){
10 d=a;
11 x=1;
12 y=0;
13 }else{
14 exgcd(b,a%b,d,y,x);
15 y-=x*(a/b);
16 }
17 }
18 ll getinv(int a,int p){
19 ll d,x,y;
20 exgcd(a,p,d,x,y);
21 return (x+p)%p;
22 }
23 int main(){
24 scanf("%lld%lld",&a,&b);
25 printf("%lld",getinv(a,b));
26 return 0;
27 }
D2T2 借教室
感觉比D1T2简单?一眼秒出线段树$O(nlogn)$做法,但是题目数据范围$n$和$m$都是$10^6$,吓得我以为有$O(n)$做法。。。又想了五分钟想出了差分+二分答案的做法,但时间复杂度还是$O(nlogm)$的,感觉不会有更好的做法,于是就码了一个,于是就过了。。。
对于每个订单打标记加到差分数组里,然后二分答案即可。
1 #include<iostream>
2 #include<cstring>
3 #include<cstdio>
4 #include<cmath>
5 using namespace std;
6 typedef long long ll;
7 int n,m,L,R,d[1000001],s[1000001],t[1000001];
8 ll pre,r[1000001],nw[1000001];
9 bool ok=true;
10 bool check(int k){
11 for(int i=1;i<=n;i++)nw[i]=0;
12 pre=0;
13 for(int i=1;i<=k;i++){
14 nw[s[i]]+=d[i];
15 nw[t[i]+1]-=d[i];
16 }
17 for(int i=1;i<=n;i++){
18 pre+=nw[i];
19 if(pre>r[i])return false;
20 }
21 return true;
22 }
23 int main(){
24 memset(nw,0,sizeof(nw));
25 scanf("%d%d",&n,&m);
26 for(int i=1;i<=n;i++){
27 scanf("%lld",&r[i]);
28 }
29 for(int i=1;i<=m;i++){
30 scanf("%d%d%d",&d[i],&s[i],&t[i]);
31 nw[s[i]]+=d[i];
32 nw[t[i]+1]-=d[i];
33 }
34 for(int i=1;i<=n;i++){
35 pre+=nw[i];
36 if(pre>r[i]){
37 ok=false;
38 break;
39 }
40 }
41 if(ok)return !puts("0");
42 L=1,R=m;
43 while(L<R){
44 int mid=(L+R)/2;
45 if(check(mid))L=mid+1;
46 else R=mid;
47 }
48 printf("-1\n%d",L);
49 return 0;
50 }
D2T3 疫情控制
(看了题解)场上写了个60分$O(n^2logn)$的暴力,详细题解请看这篇博客
总结:
1.今天时间不充裕,没有严格按照noip时间来写题,随便做了做,三个小时干完两天的前两题之后就对着两道T3发呆,然而并没有搞出来,这样不能很好地模拟考场上的环境,以后要注意;
2.简单题要尽量写快点,不要总是出弱智错,对节奏有很大影响;
3.看到T3不要慌,实际上题解并不会用到很高级的算法(noip不考数据结构flag++),D1T3是个神秘倍增,D2T3大贪心,严格来说都不算很难,要仔细分析题目+熟练使用各种基础思想(二分,贪心等),放宽心态,不要让思想进入死胡同。
4.SBLOJ!