链接:http://codeforces.com/contest/1114
A - Got Any Grapes?
题意:甲乙丙三个人吃葡萄,总共有三种葡萄:绿葡萄、紫葡萄和黑葡萄,甲乙丙三个人至少要各自吃 $x,y,z$ 个葡萄,又甲只吃绿葡萄,乙不吃黑葡萄,丙三种颜色都吃。现在总共有 $a$ 个绿葡萄、$b$ 个紫葡萄、$c$ 个黑葡萄。要确认这么多葡萄能否使得三个人都满意。
题解:优先满足甲,剩下的绿葡萄加紫葡萄满足乙,最后再剩下的看看能不能满足丙。
AC代码:
#include<bits/stdc++.h> using namespace std; int x,y,z,a,b,c; int main() { cin>>x>>y>>z>>a>>b>>c; if(a>=x && (a-x)+b>=y && a-x+b-y+c>=z) cout<<"YES "; else cout<<"NO "; }
B - Yet Another Array Partitioning Task - [贪心]
题意:将一个长度为 $n$ 的数组分成 $k$ 段,每段元素至少要有 $m$ 个,设每一段都有一个“漂亮程度”为该段的前 $m$ 大的元素之和,求如何分割这个数组,使得每一段的“漂亮程度”之和最大。
题解:这个数组的前 $m cdot k$ 大个元素就是答案,假设这 $m cdot k$ 个元素的集合为 $S$。那么如何分割,即在原数组中枚举,每累计遇到 $m$ 个属于 $S$ 的元素,就在这个位置划一刀,然后重新开始累计即可。
AC代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; typedef pair<int,int> pii; #define mk(x,y) make_pair(x,y) #define a(p) (p.first) #define b(p) (p.second) const int maxn=2e5+5; int n,m,k; int a[maxn]; bool t[maxn]; vector<pii> v; int main() { cin>>n>>m>>k; for(int i=1;i<=n;i++) scanf("%d",&a[i]), v.push_back(mk(a[i],i)); sort(v.begin(),v.end(),greater<pii>()); memset(t,0,sizeof(t)); ll sum=0; for(int i=0;i<(m*k);i++) t[b(v[i])]=1, sum+=a(v[i]); cout<<sum<<endl; int cnt=0, seg=0; for(int i=1;i<=n;i++) { if(t[i]) cnt++; if(cnt==m) { cnt=0, seg++; if(seg<k) printf("%d ",i); } } }
C - Trailing Loves (or L'oeufs?) - [分解质因数]
题意:给定一个十进制下的 $n$,要求出 $n!$ 在 $b$ 进制下进行表示,其尾部有多少个零。
题解:
换句话说,其实就是算 $n!$ 最多能整除多少次 $b$。对 $b$ 分解质因数得 $b = {p_1}^{x_1}{p_2}^{x_2} cdots {p_k}^{x_k}$,然后只要算出 $n!$ 对应的 ${p_1}^{y_1}{p_2}^{y_2} cdots {p_k}^{y_k}$ 就很好办了。
然后用十进制举例,例如 $n = 12$,那么先考虑 $10$ 进制的 $10 = 2 imes 5$,然后 $n / 2 = 6$ 我们就可以知道 $1 sim 12$ 有 $[2,4,6,8,10,12]$ 这六个 $2$ 的倍数,每个数至少能提供 $2$ 的 $1$ 次方,而其中又有一部分能多提供一些,即 $n / 2^2 = 3$,即有 $[4,8,12]$ 这三个数字至少能提供 $2$ 的 $2$ 次方,考虑到这三个数字前面已经计算过提供一次,所以可以看做这三个数又提供了三个 $1$ 次方,再然后 $n / 2^3 = 12 / 8 = 1$,即 $[8]$ 这一个数字还能再多提供一个 $1$ 次方。因此总结下来,计算 $n!$ 到底包含多少个 $p_i$ 的 $x$ 次方通过 $n/p_i + n/p_i^2 + cdots $ 就可以算出来。
所以类似地,我们对 $p_1 cdots p_k$,每个都算出 $n!$ 包含其多少次方,最后取 $lfloor frac{y_i}{x_i} floor$ 的最小值。
AC代码:
#include<bits/stdc++.h> using namespace std; typedef unsigned long long ull; typedef pair<ull,int> P; #define mk(x,y) make_pair(x,y) #define _1 first #define _2 second vector<P> p; void dec(ull n) { p.clear(); for(ull i=2;i*i<=n;i++) { if(n%i==0) { int cnt=0; while(n%i==0) n/=i, cnt++; p.push_back(mk(i,cnt)); } } if(n>1) p.push_back(mk(n,1)); } ull solve(ull n,ull p) { ull base=1, res=0; while(base<=n/p) { base*=p; res+=n/base; } return res; } int main() { ull n,b; cin>>n>>b; dec(b); ull ans=ULLONG_MAX; for(auto x:p) ans=min(ans,solve(n,x._1)/x._2); cout<<ans<<endl; }
D - Flood Fill - [区间DP]
题意:在一条线上给定 $n$ 个方格,每个方格都上了一种颜色,当尽量最长的一段的若干个连续方格颜色一样时,可以看做一个连通块。我们开始时要选择一个起点方格,然后我们可以不断地将该方格所属的整个连通块全部变为另一个颜色,每变一次算一轮,求最少多少轮可以使得 $n$ 个方格变为一个连通块。
题解:首先我们知道,连通块是逐渐扩张的,而且一个连通块的颜色,要么是最左边的那个方块的颜色,要么是最右边的方块的颜色。
因此记 $f[0,1][i][j]$ 表示 $[i,j]$ 区间内所有方块变成最左侧方块颜色的最少轮次,以及变成最右侧方块颜色的最少轮次。
那么显然初始状态就是所有的 $f[0,1][i][i] = 0$,而对于一个 $f[0,1][i][j]$,它可能是 $f[0,1][i+1][j]$ 转移过来的,也可能是 $f[0,1][i][j-1]$ 转移过来的,若 $c[i]=c[j]$ 的话那么还有可能是 $f[0,1][i+1][j-1]$ 转移过来的。
AC代码:
#include<bits/stdc++.h> using namespace std; const int maxn=5e3+5; int n,c[maxn]; int f[2][maxn][maxn]; int main() { cin>>n; for(int i=1;i<=n;i++) cin>>c[i]; memset(f,0x3f,sizeof(f)); for(int i=1;i<=n;i++) f[0][i][i]=f[1][i][i]=0; for(int len=2,tp;len<=n;len++) { for(int i=1,j=i+len-1;j<=n;i++,j++) { tp=min(f[0][i+1][j]+(c[i]!=c[i+1]),f[1][i+1][j]+(c[i]!=c[j])); f[0][i][j]=min(f[0][i][j],tp); tp=min(f[0][i][j-1]+(c[i]!=c[j]),f[1][i][j-1]+(c[j-1]!=c[j])); f[1][i][j]=min(f[1][i][j],tp); if(c[i]==c[j]) { tp=min(f[0][i+1][j-1]+(c[i+1]!=c[i]),f[1][i+1][j-1]+(c[j-1]!=c[j])); f[0][i][j]=min(f[0][i][j],tp), f[1][i][j]=min(f[1][i][j],tp); } } } cout<<min(f[0][1][n],f[1][1][n])<<endl; }
PS.作为一个DP废物,每每遇到DP题就很艰辛……