2000NOIP提高组(全解)
题目链接与说明:
进制转换(负进制的处理)
https://www.luogu.com.cn/problem/P1017
乘积最大(DP+大整数)
https://www.luogu.com.cn/problem/P1018
单词接龙(DFS+暴力)
https://www.luogu.com.cn/problem/P1019
方格取数(DP)
https://www.luogu.com.cn/problem/P1004
进制转换
如果是一般的进制转换的话很容易做,我们一直取余然后将其倒置输出就OK了,比如:
14(10)=14/2=7...0
7/2=3...1
3/2=1...1
1/2=0...1
那么也就是说10进制下的14在2进制下表示为1110
但这里变成了负进制的话就需要改改了。我们必须知道的是做除法的时候余数不能为负数,那么事情就好办了,我们一样地做除法,一旦碰到余数为负数的情况,我们将商+1,同时将余数减去被除数就好了,比如:
14(10)=14/-2=-7...0
-7/-2=3...-1
3+1=4,-1-(-2)=1
4/-2=-2...0
-2/-2=1...0
1/-2=0...1
所以14在-2进制下的表示为10010
那么程序也就很简单了
以下是AC代码:
#include <bits/stdc++.h> using namespace std; vector<int>g; char use[]={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L'}; int main() { int n,r; scanf ("%d%d",&n,&r); int cpn=n; while(n){ int p=n%r; n/=r; printf("%d %d\n",n,p ); if (p<0) p-=r,n+=1; g.push_back(abs(p)); } reverse(g.begin(),g.end()); printf ("%d=",cpn); for (auto x:g) printf ("%c",use[x]); printf ("(base%d)\n",r); return 0; }
乘积最大
emmm,一眼望过去就知道是个DP,设置$dp[i][j]$表示为第$i$位数后面放第$j$个乘号的最大值。第一个乘号不用转移,直接将前面的字符串截取就好了,从第二个乘号开始转移,我们往前寻找,如果当前要放的是第$j$个乘号,那么我们就看看前面每一位是否存在第$j-1$个乘号,如果存在就进行转移,同时转移的时候打上存在标记。那么转移的方程就挺好写了,转移部分代码如下:
for (int i=1; i<n; i++) { //在第i位后放第j个乘号 dp[i][1]=deal(1,i); dp[i][1].vis=1; for (int j=2; j<=m; j++) for (int k=j-1; k<i; k++) { //第k个位置后面是否存在第j-1个乘号,并转移 if (!dp[k][j-1].vis) continue; dp[i][j]=Max(dp[i][j],mul(dp[k][j-1],deal(k+1,i))); dp[i][j].vis=1; } if (!dp[i][m].vis) continue; ans[i]=mul(dp[i][m],deal(i+1,n)); }
接下来就是大整数部分,这个就没什么好说的了。
值得注意的是,ans[i]表示的最后一个乘号放在第$i$位的最大值,所以我们还得再循环一遍将其中的最大值找出来
以下是AC代码:
#include <bits/stdc++.h> using namespace std; const int mac=50; char s[50]; int a[mac]; struct node { int v,c[600],vis; node (){v=0; vis=0; memset(c,0,sizeof c);} void rev(){reverse(c+1,c+1+v);} }dp[mac][10],ans[mac]; node deal(int st,int ed) { node b; int p=0; for (int i=st; i<=ed; i++) b.c[++p]=a[i]; b.v=ed-st+1; return b; } node Max(node x,node y) { if (x.v==y.v){ for (int i=1; i<=x.v; i++){ if (x.c[i]>y.c[i]) return x; if (x.c[i]<y.c[i]) return y; } } if (x.v>y.v) return x; return y; } node mul(node x,node y) { node z; int cnt=0,base=0,tail1=x.v,tail2=y.v; for (int i=tail1; i>=1; i--){ cnt++;base=0; for (int j=tail2; j>=1; j--){ z.c[cnt+base]+=x.c[i]*y.c[j]; z.v=max(z.v,cnt+base); base++; } } for (int i=1; i<=z.v; i++){ if (i>100) break; if (z.c[i]>=10) { z.c[i+1]+=z.c[i]/10; z.c[i]%=10; if (i==z.v) z.v++; } } z.rev(); return z; } int main() { int n,m; scanf ("%d%d",&n,&m); scanf ("%s",s+1); for (int i=1; i<=n; i++) a[i]=s[i]-'0'; for (int i=1; i<n; i++){//在第i位后放第j个乘号 dp[i][1]=deal(1,i); dp[i][1].vis=1; for (int j=2; j<=m; j++) for (int k=j-1; k<i; k++){//第k个位置后面是否存在第j-1个乘号,并转移 if (!dp[k][j-1].vis) continue; dp[i][j]=Max(dp[i][j],mul(dp[k][j-1],deal(k+1,i))); dp[i][j].vis=1; } if (!dp[i][m].vis) continue; ans[i]=mul(dp[i][m],deal(i+1,n)); } node last_ans; for (int i=1; i<n; i++) last_ans=Max(last_ans,ans[i]); for (int i=1; i<=last_ans.v; i++) printf("%d",last_ans.c[i]); printf("\n"); return 0; }
单词接龙
emmm,感觉没什么好说的,就是个暴力DFS的过程,不过用string来做的话感觉容易一些。对每个含龙头字母的字符串开始DFS,然后对每一个字符串判断是否能拼接,并且得到重复的长度,如下:
int ok(string p1,string p2,int &same) { int len1=p1.length(),len2=p2.length(); int head1=1,head2=0,head=0;//重复的不要,所以head1从1开始 for (; head1<len1; head1++){ if (p1[head1]==p2[head2]){ int cphead=head1; while(p1[head1]==p2[head2]){ head1++;head2++; if (head2==len2) break; if (head1==len1) break; } if (head1<len1 || head2==len2) head1=cphead,head2=0; else head=max(head,cphead),head1=cphead,head2=0; } } if (head) { same=len1-head; return 1; } return 0; }
接下来就是个简单的DFS过程了,进行OK判断完成之后将字符串进行拼接,我们可以使用substr函数来进行,同时在回溯的时候我们可以用erase函数来抹除上一次的拼接
以下是AC代码:
#include <bits/stdc++.h> using namespace std; string s1,s,word[30]; int tot=0,ans=0,vis[30]; int ok(string p1,string p2,int &same) { int len1=p1.length(),len2=p2.length(); int head1=1,head2=0,head=0;//重复的不要,所以head1从1开始 for (; head1<len1; head1++){ if (p1[head1]==p2[head2]){ int cphead=head1; while(p1[head1]==p2[head2]){ head1++;head2++; if (head2==len2) break; if (head1==len1) break; } if (head1<len1 || head2==len2) head1=cphead,head2=0; else head=max(head,cphead),head1=cphead,head2=0; } } if (head) { same=len1-head; return 1; } return 0; } void dfs(string now,int len,int same) { for (int i=1; i<=tot; i++){ if (vis[i]==2) continue; if (!ok(now,word[i],same)) continue; len+=word[i].length()-same; ans=max(ans,len); vis[i]++; now+=word[i].substr(same); dfs(now,len,same); len-=word[i].length()-same; now.erase(now.begin()+len,now.end()); vis[i]--; } } int main() { int n,p=0; cin>>n; for (int i=1; i<=n; i++){ cin>>s; if (s.length()>=2) word[++p]=s; } cin>>s; tot=p; for (int i=1; i<=tot; i++){ if (word[i][0]==s[0]) { int uu=word[i].length(); ans=max(ans,uu); vis[i]=1; dfs(word[i],uu,0); vis[i]=0; } } printf ("%d\n",ans); return 0; }
方格取数
这题也没什么好说的,就是个裸的方格DP,最为简单的方法就是直接定义四维DP,$dp[i][j][k][l]$表示了第一个走到坐标$(i,j)$,第二个走到坐标$(k,l)$的时候的最大值,那么状态的转移也非常易懂,每个人的决策有两种,那么总共也就4种决策,我们的$dp[i][j][k][l]$也就是从上一个最优决策加上本次的收获,但需要值得注意的是,当坐标相同的时候要减去本次的收获。
以下是AC代码:
#include <bits/stdc++.h> using namespace std; int dp[12][12][12][12]; int mp[12][12]; int main(int argc, char const *argv[]) { int n; scanf ("%d",&n); int x,y,val; while (scanf ("%d%d%d",&x,&y,&val)){ if (!x && !y && !val) break; mp[x][y]=val; } for (int i=1; i<=n; i++) for (int j=1; j<=n; j++) for (int k=1; k<=n; k++) for (int l=1; l<=n; l++){ dp[i][j][k][l]=max(max(dp[i-1][j][k-1][l],dp[i-1][j][k][l-1]),max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1]))+mp[i][j]+mp[k][l]; if (i==k && j==l) dp[i][j][k][l]-=mp[i][j]; } printf ("%d\n",dp[n][n][n][n]); return 0; }