【学习笔记】CF1314
非常标准的oi风格题目
这题很脑洞啊。首先你要强行把树的结构构建出来,然后对于败者组,显然如果一个节点是 1 1 1那么就会被保留下来,那么符合条件的比赛数目可以转化为除了叶子节点外为 1 1 1的节点的个数。注意到如果一个节点是 1 1 1,那么其祖先节点一定都是 1 1 1,我们可以据此设计 d p dp dp。
具体的,我们只保留败者组当中全为
0
0
0的子树,然后看在哪一层第一次出现为
1
1
1的节点,注意这里算答案的时候要用总点数减去全为
0
0
0的子树。当然这两棵树的贡献是不能直接独立计算的,这也是这道题最烦人的地方 。
那么我们考虑把两颗子树的信息合并一下,假设是合并到了胜者组的那颗树上,考虑一颗子树,发现非常好转移。于是我们直接在这棵树上做树 d p dp dp,这道题就做完了。
复杂度 O ( 2 n ) O(2^n) O(2n)。
#include<bits/stdc++.h>
#define pb push_back
#define ll long long
#define inf 0x3f3f3f3f3f3f3f3f
#define fi first
#define se second
using namespace std;
const int N=1<<20;
int n,K,dp[N][2][2],vs[N],res;
void chmax(int &x,int y){
x=max(x,y);
}
void dfs(int p,int l,int r){
if(l+1==r){
if(vs[l]&&vs[r]){
dp[p][1][1]=1;
}
else if(!vs[l]&&!vs[r]){
dp[p][0][0]=0;
}
else{
dp[p][1][0]=dp[p][0][1]=1;
}
return;
}
int mid=l+r>>1;
dfs(p<<1,l,mid),dfs(p<<1|1,mid+1,r);
for(int i=0;i<2;i++){
for(int j=0;j<2;j++){
for(int k=0;k<2;k++){
for(int l=0;l<2;l++){
if(i&&k){
chmax(dp[p][1][1],dp[p<<1][i][j]+dp[p<<1|1][k][l]+(j||l)+2);
}
else if(!i&&!k){
chmax(dp[p][0][j||l],dp[p<<1][i][j]+dp[p<<1|1][k][l]+(j||l)+(j||l));
}
else{
chmax(dp[p][1][j|l],dp[p<<1][i][j]+dp[p<<1|1][k][l]+(j||l)+(j||l)+1);
chmax(dp[p][0][1],dp[p<<1][i][j]+dp[p<<1|1][k][l]+(j||l)+2);
}
}
}
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>K;memset(dp,-0x3f,sizeof dp);
for(int i=1;i<=K;i++){
int x;cin>>x,vs[x]=1;
}
dfs(1,1,1<<n);
for(int i=0;i<2;i++){
for(int j=0;j<2;j++){
chmax(res,dp[1][i][j]+(i||j));
}
}
cout<<res;
}
非常高消啊,之前应该做过这种找第
K
K
K大的问题。最基本的思路是二分答案,然后问题转化为对划分方案计数,直接
d
p
dp
dp就行了。非常套路的题。主要是代码细节有点多
复杂度 O ( n 2 log n ) O(n^2\log n) O(n2logn)。
#include<bits/stdc++.h>
#define pb push_back
#define ll long long
#define inf 0x3f3f3f3f3f3f3f3f
#define fi first
#define se second
using namespace std;
const int N=1005;
int n,m,cnt;
ll K,dp[N][N],sum[N][N];
string S,s[N];
string find(int rank){
string res;
int tot=0;
for(int i=1;i<=n;i++){
int j=0;
while(i!=n&&j<min(s[i].size(),s[i+1].size())&&s[i][j]==s[i+1][j])j++;
if(tot+s[i].size()-j>=rank){
assert(s[i].size()>=rank-tot);
for(int k=0;k<=s[i].size()-(rank-tot);k++){
res+=s[i][k];
}
return res;
}
tot+=s[i].size()-j;
}
return "";
}
ll solve(int mid){
memset(dp,0,sizeof dp),memset(sum,0,sizeof sum),dp[n+1][0]=sum[n+1][0]=1;
string res=find(mid);
for(int i=n;i>=1;i--){
int x=i;
string tmp;
tmp+=S[i-1];
while(x<=n&&tmp<res){
if(x!=n)tmp+=S[x];
x++;
}
for(int j=m;j>=0;j--){
if(j)dp[i][j]=sum[x+1][j-1];
sum[i][j]=sum[i+1][j]+dp[i][j];
if(sum[i][j]>K)sum[i][j]=K;
}
}
return dp[1][m];
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m>>K>>S;
for(int i=n;i>=1;i--){
s[i]=S[i-1]+s[i+1];
}
sort(s+1,s+1+n),reverse(s+1,s+1+n);
int l=1,r=n*(n+1)/2,res=0;
while(l<=r){
int mid=l+r>>1;
if(find(mid)==""||solve(mid)>=K){
res=mid,r=mid-1;
}
else{
l=mid+1;
}
}
assert(res);
cout<<find(res);
}
先不考虑时间复杂度,难点在于如何去重。
我们用 { a i } \{a_i\} {ai}描述这个可重集,其中 a i a_i ai表示 i i i在集合中的出现次数。显然,初始我们有 ∑ a i ≤ n \sum a_i\le n ∑ai≤n。
这个限制看着很亲切啊,因为如果知道了 f ( a ) f(a) f(a)就能推出 a a a,如果两个 f ( a ) f(a) f(a)同构的话(这里指生成的后续集合完全相同),那么我们应该将数字从大到小排列,贪心的使得 ∑ a i \sum a_i ∑ai最小。
毛估一下显然
K
K
K不会很大,嗯,这里是猜到了分析思路的,不过下次可能需要分析的更快一点。 因此
K
≥
3
K\ge 3
K≥3的时候直接暴搜剪一剪枝就能过。
K
=
1
K=1
K=1是一维背包问题,可以暴力
O
(
n
2
)
O(n^2)
O(n2)解决。
K
=
2
K=2
K=2是二维背包问题,但是细致分析发现前两维的乘积不会超过第三维,因此复杂度
O
(
n
2
log
n
)
O(n^2\log n)
O(n2logn)。当然我这个估计其实并不精确。我们可以将问题转换一下,也就是拆分成若干个后缀
1
1
1的形式,也就是有
n
n
n个物品,第
i
i
i个物品的体积为
i
(
i
+
1
)
2
\frac{i(i+1)}{2}
2i(i+1),问总体积不超过
n
n
n的方案数。显然这是一个经典的完全背包问题,复杂度
O
(
n
1.5
)
O(n^{1.5})
O(n1.5)。
我也是一个脑瘫,竟然会想到用map实现背包
明明可以做的更快的,结果还是耗费了大量的时间
#include<bits/stdc++.h>
#define pb push_back
#define fi first
#define se second
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int mod=998244353;
int n,K,res;
vector<int>p;
int check(){
vector<int>p2=p,p3;
int tot=0;for(int i=0;i<p2.size();i++)tot+=p2[i];
if(tot>n)return 0;
for(int i=K-2;i>=0;i--){
int tot=0;
assert(p2.size());
p3.clear();
for(int j=p2.size()-1;j>=0;j--){
for(int k=0;k<p2[j];k++){
p3.pb(j+1);
tot+=j+1;
if(tot>n)return 0;
}
}
p2=p3;
}
return 1;
}
void dfs(int x){
if(x>1){
res++;
}
int tp=p.size()?p.back():2020;
for(int i=1;i<=tp;i++){
p.pb(i);
if(check()){
dfs(x+1);
p.pop_back();
}
else{
p.pop_back();
return;
}
}
}
int dp[2025];
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>K;
if(K>=3){
dfs(1);
cout<<res;
}
else if(K==2){
dp[0]=1;
for(int i=1;i*(i+1)/2<=n;i++){
for(int j=i*(i+1)/2;j<=n;j++){
dp[j]=(dp[j]+dp[j-i*(i+1)/2])%mod;
}
}
for(int i=1;i<=n;i++)res=(res+dp[i])%mod;
cout<<res;
}
else{
dp[0]=1;
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
dp[j]=(dp[j]+dp[j-i])%mod;
}
}
for(int i=1;i<=n;i++)res=(res+dp[i])%mod;
cout<<res;
}
}

浙公网安备 33010602011771号