【学习笔记】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 ain

这个限制看着很亲切啊,因为如果知道了 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 K3的时候直接暴搜剪一剪枝就能过。 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;
    }
}
posted @ 2023-03-25 11:31  仰望星空的蚂蚁  阅读(45)  评论(0)    收藏  举报  来源