Codeforces Round #191 (Div. 2) 解题报告
------------
A. Flipping Game
有n个整数a1, a2, ..., an,每个整数只可能为0或1。
选择一个区间[i, j](i<=j)翻转,翻转表示X=1-X。
目标是求一次翻转后1的最大数目。
----
将0变为1,将1变为-1,1次翻转后1的最大数目=原序列中1的数目+最大连续子序列和。
问题转化为求最大连续子序列和,dp解决。
注意一定要翻转一次,这是个坑。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int INF=1e9; int a[111]={0}; int f[111]={0}; int n; int main() { int ans; int bas; while (cin>>n) { ans=-INF; bas=0; for (int i=1;i<=n;i++) { cin>>a[i]; if (a[i]) {a[i]=-1; bas++; } else if (!a[i]) a[i]=1; } for (int i=1;i<=n;i++) { f[i]=max(f[i-1]+a[i],a[i]); ans=max(ans,f[i]); } cout<<bas+ans<<endl; } return 0; }
------------
B. Hungry Sequence
寻找一个长度为n(1 ≤ n ≤ 105)的饥饿序列。
一个序列为饥饿序列,当且仅当:
①若i<j,则ai<aj。
②若i<j,则aj不能被ai整除。
(1 ≤ ai ≤ 107)
找到任意一个序列即可。
----
最简单的序列:n+1, n+2, n+3....n+n。
简单证明:(n+i)*2=n+n+i*2>n+n
另一种方法:
前sqrt(n)个素数两两相乘,排序后输出即可。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> using namespace std; int n; int main() { while (cin>>n) { for (int i=1;i<=n;i++) cout<<n+i<<" "; cout<<endl; } return 0; }
------------
C. Magic Five
一个n位长的数字S,从中删除几个数字(可以不删除但不能全部删除),得到一个能被5整除的数,称为幻数。
问能得到幻数的方法有多少种(结果模1000000007)。如果删除的数字位置不同则两种方法不同。
S由一个特殊形式给出。
输入一个仅包含数字的字符串a (1 ≤ |a| ≤ 105),和一个整数k(1 ≤ k ≤ 109),S由k个a连接而成。
----
当一个数字个位数为0或5的时候才能被5整除。
将一个能被5整除的n位数删除一些数字,使其还能被5整除有:
种方案。
假设a的第i位为0或5,则以该位为尾数的方案数为2^i。
对于第i位的k次重复,方案数为:2^i+2^(i+n)+2^(i+2*n)+...+2^(i+k*n)即一个首项为2^i公比为2^n的等比数列之和。
接下来用到的一些定理:
a/b%M=a*(b的逆元)%M。
证明:
M=10^9+7为素数,b的逆元为b^(p-2)%p。
证明:
则以a的第i位为尾数的方案数:
更优的代码:
#include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #include <iostream> using namespace std; typedef long long LL; const LL MOD=1000000007; LL quick_mod(LL a,LL b,LL m) { LL ans=1; while (b) { if (b&1) { ans=(ans*a)%m; b--; } b/=2; a=a*a%m; } return ans; } int main() { char s[111111]; int n,k; LL ans,q; while (cin>>s>>k) { ans=0; n=strlen(s); q=quick_mod(2,n,MOD); for (int i=0;i<n;i++) { if (s[i]=='0'||s[i]=='5') { ans+=quick_mod(2,i,MOD)%MOD; ans%=MOD; } } ans=ans*(quick_mod(q,k,MOD)-1+MOD)%MOD*quick_mod(q-1,MOD-2,MOD)%MOD; cout<<ans<<endl; } return 0; }
优化前:
#include <cstdio> #include <cstring> #include <cmath> #include <algorithm> #include <iostream> using namespace std; typedef long long LL; const LL MOD=1000000007; LL quick_mod(LL a,LL b,LL m) { LL ans=1; while (b) { if (b&1) { ans=(ans*a)%m; b--; } b/=2; a=a*a%m; } return ans; } int main() { char s[111111]; int n,k; LL ans,q; while (cin>>s>>k) { ans=0; n=strlen(s); q=quick_mod(2,n,MOD); for (int i=0;i<n;i++) { if (s[i]=='0'||s[i]=='5') { ans+=quick_mod(2,i,MOD)%MOD *(quick_mod(q,k,MOD)-1+MOD)%MOD *quick_mod(q-1,MOD-2,MOD)%MOD; ans%=MOD; } } cout<<ans<<endl; } return 0; }------------
D. Block Tower
有一个n*m的矩形网格,其中#不能建造,其余是空的。
有两种塔:蓝塔(容纳100人),红塔(容纳200人)。
红塔只有在与蓝塔相邻(公边)时才能建造。
有三种操作:
B x y:在(x,y)建立一座蓝塔。
R x y:在(x,y)建立一座红塔。
D x y:摧毁(x,y)处的塔,摧毁一座塔对其它已建成的塔没有影响。
找到一个能容纳最多人口的操作序列 。不要求序列最短。
----
为了使人口最大应该尽可能的建造红塔。
可以发现网络中的每一个连通区域只需要保留1座蓝塔。
由于不要求序列最短,可以先将一个区域建造满蓝塔,再按照建造的路径拆除蓝塔改建红塔。
最后只保留第一个蓝塔即可。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <vector> using namespace std; const int maxn=555; const int direct[4][2]={ {1,0},{0,1},{-1,0},{0,-1} }; struct ANS_NODE{ int x; int y; char c; ANS_NODE(int _x,int _y,char _c):x(_x),y(_y),c(_c){} }; char map[maxn][maxn]; bool visit[maxn][maxn]; vector<ANS_NODE>ans; vector<ANS_NODE>::iterator it; int n,m; bool check(int x,int y) { if (x>=1&&x<=n&&y>=1&&y<=m) return true; return false; } void dfs(int x,int y,int d) { visit[x][y]=true; ans.push_back(ANS_NODE(x,y,'B')); for (int i=0;i<4;i++) { int dx=x+direct[i][0]; int dy=y+direct[i][1]; if (check(dx,dy)&&map[dx][dy]!='#'&&!visit[dx][dy]) { dfs(dx,dy,d+1); } } if (d) { ans.push_back(ANS_NODE(x,y,'D')); ans.push_back(ANS_NODE(x,y,'R')); } } int main() { while (cin>>n>>m) { ans.clear(); memset(visit,0,sizeof(visit)); for (int i=1;i<=n;i++) cin>>(map[i]+1); for (int i=1;i<=n;i++) { for (int j=1;j<=m;j++) { if (!visit[i][j]&&map[i][j]!='#') dfs(i,j,0); } } cout<<ans.size()<<endl; for (it=ans.begin();it!=ans.end();it++) { cout<<it->c<<" "<<it->x<<" "<<it->y<<endl; } } return 0; }
------------
E. Axis Walking
有n个(n<=24)数字a1,a2,a3...an,和为d。
中间过程和有k个数不能出现,问从0加到d有多少种方法。
----
可以用状态压缩dp卡时间过。。
sum[S]表示集合S中的数字之和。
f[S]表示当前状态为S时的移动方案数。
当sum[S]为不能出现的数字时,f[S]=0;
其他情况 f[S]+=f[S^(1<<i)] ,i属于集合S。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cmath> using namespace std; typedef long long LL; const int maxn=24; const int maxs=1<<24; const LL MOD=1000000007; int n,m; LL a[maxn]={0}; LL sum[maxs]={0}; LL b[maxn]={0}; LL f[maxs]={0}; int main() { //init() cin>>n; for (int i=0; i<n; i++) cin>>a[i]; cin>>m; for (int i=0; i<m; i++) cin>>b[i]; //prepare() sum[0]=0; for (int s=1; s<(1<<n); s++) { int k=__builtin_ctz(s); sum[s]=sum[s^(1<<k)]+a[k]; for (int i=0; i<m; i++) if (sum[s]==b[i]) f[s]=-1; } f[0]=1; for (int s=1; s<(1<<n); s++) { if (f[s]<0) f[s]=0; else { for (int i=0; i<n; i++) if (s&(1<<i)) f[s]+=f[s^(1<<i)]; f[s]%=MOD; } } cout<<f[(1<<n)-1]<<endl; return 0; }
------------