2023.3.28 总结

CF960F Pathwalks
正解:线段树优化dp,转移很简单,类似于LIS

解法二:开一个map,然后 m a p i , j map_{i,j} mapi,j代表通过编号为 j j j这一条边,到第 i i i个点时的最大长度

m a p v , j = m a x ( m a p u , k ) map_{v,j}=max(map_{u,k} ) mapv,j=max(mapu,k) —> k用map自带的迭代器枚举( k < j kk<j,我们从小通过在枚举编号为 j j j的边dp时,这个条件一定满足) , m a p map map k k k会从小到大枚举 因为要通过枚举找到最大的 m a p u , k map_{u,k} mapu,k,所以不够高效
那么我们考虑二分查找,但二分要求随 k k k的增大, m a p u , k map_{u,k} mapu,k也增大,所以我们可以用类似单调队列的思想,如果我们每次更新后 m a p v , j map_{v,j} mapv,j,存在 m a p v , l < m a p v , j map_{v,l}mapv,l<mapv,j l > j l>j l>j),那么 m a p v , l map_{v,l} mapv,l一定不会是之后的最优转移,这样就保证了 m a p v , k map_{v,k} mapv,k中的单调性

#include 
#define ll long long
using namespace std;
map<int,int> dp[100005];//维护的map第二关键字随第一关键字的增大而增大
int find(int u,int w){
    auto it=dp[u].lower_bound(w);
    return it==dp[u].begin()?0:(--it)->second;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    int n,m;
    cin>>n>>m;
    int ans=0;
    for (int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        int maxn=find(u,w)+1;//找到到达u点,最后一段路价值小于w的最长距离+1
        if (maxn>find(v,w)){
            auto it=dp[v].lower_bound(w);
            dp[v][w]=maxn;
            while (it!=dp[v].end()&&it->second<maxn){//保证map第二关键字单调
                dp[v].erase(it++);//it迭代删除后面w大但距离小的点
            }
            ans=max(ans,maxn);
        }
    }
    cout<<ans<<"\n";
    return 0;
}

CF417D Cunning Gena
状压dp , d p i , j dp_{i,j} dpi,j表示前 i i i个人中,已经能做出的题目状态为 j j j的最小花费(不包括显示屏)
预处理每个人能做的题目的状态 用或|运算转移即可
可以用滚动数组优化掉一维空间
每次枚举一个 i i i更新一下当前做出所有题目的最小花费 a n s ans ans

#include 
#define ll long long
using namespace std;
struct node{
    ll cost,val,tol;
};
bool cmp(node x,node y){
    return x.tol<y.tol;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    int n,m,b;
    cin>>n>>m>>b;

    vector<node> a(n+1);
    for (int i=1;i<=n;i++){
        int x;
        cin>>a[i].cost>>a[i].tol>>x;
        int temp=0;
        while (x--){
            int y;
            cin>>y;
            temp|=(1<<(y-1));
        }
        a[i].val=temp;
    }

    sort(a.begin(),a.end(),cmp);

    vector<vector<ll>> dp(2,vector<ll>(1<<m,2e18+5));
    int now=0;
    dp[0][0]=0;
    ll ans=2e18+5;
    for (int i=1;i<=n;i++){
        for (int j=0;j<(1<<m);j++){
            dp[now^1][j|(a[i].val)]=min(dp[now^1][j|(a[i].val)],dp[now][j]+a[i].cost);
            dp[now^1][j]=min(dp[now^1][j],dp[now][j]);
            if ((j|a[i].val)==(1<<m)-1){
                ans=min(ans,a[i].tol*b+dp[now^1][(1<<m)-1]);
            }
            dp[now][j]=2e18+5;
        }
        now^=1;
    }
    if (ans==2e18+5){
        cout<<"-1";
    }else{
        cout<<ans<<"\n";
    }
    return 0;
}

CF893E Counting Arrays
排列组合,考虑 n n n中每个质因子的贡献
比如 n n n 中质因子 m m m s u m sum sum个质因子,那么这 s u m sum sum个质因子可以任意分配到 y y y 段中,用隔板法进行统计,组合的计算求逆元

阶乘和阶乘的逆元预处理一下即可

#include 
#define ll long long
using namespace std;
const ll mod=1e9+7;

ll fac1[1000025],fac2[1000025];

ll ksm(ll a,ll x){
    ll s=a,sum=1;
    while (x){
        if (x&1){
            sum=(sum*s)%mod;
        }
        s=(s*s)%mod;
        x>>=1;
    }
    return sum;
}

ll ni(ll x){
    return ksm(x,mod-2);
}

void solve(){
    int x,y;
    cin>>x>>y;
    vector<int> sum;
    for (int i=2;i*i<=x;i++){
        if (x%i==0){
            int tol=0;
            while (x%i==0){
                x/=i;
                tol++;
            }
            sum.push_back(tol);
        }
    }
    if (x!=1){
        sum.push_back(1);
    }

    ll ans=ksm(2,y-1);
    for (auto i:sum){
        // cout<
        ans=ans*(fac1[i+y-1]*fac2[y-1]%mod*fac2[i]%mod)%mod;
    }
    cout<<ans<<"\n";

}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    fac1[0]=fac1[1]=1;
    for (int i=2;i<=1000020;i++){
        fac1[i]=(fac1[i-1]*i)%mod;
    }
    fac2[0]=fac2[1]=ni(1);
    for (int i=2;i<=1000020;i++){
        fac2[i]=(fac2[i-1]*ni(i))%mod;
    }

    int t;
    cin>>t;
    while (t--){
        solve();
    }
    return 0;
}

CF167B Wizards and Huge Prize
概率dp的简单题,每一步决策很简单就是输或赢
d p i , j , k dp_{i,j,k} dpi,j,k表示枚举到第 i i i件物品,赢了 j j j次,背包空间剩下 k k k体积时的概率 注意一下边界范围 初始化就好

然后枚举时可能会出现物品对空间价值为-1,导致背包空间暂时为负数的情况,所以可以先选(更新)价值为正的物品,排序即可

#include 
#define ll long long
using namespace std;
struct node{
    int p,val;
};
double dp[205][205][205];
bool cmp(node x,node y){
    return x.val>y.val;
}
int main(){
    
    int n,l,v;
    cin>>n>>l>>v;
    vector<node> a(n+1);
    for (int i=1;i<=n;i++){
        scanf("%d",&a[i].p);
    }
    for (int i=1;i<=n;i++){
        scanf("%d",&a[i].val);
    }

    sort(a.begin()+1,a.end(),cmp);

    dp[0][0][v]=1;
    for (int i=1;i<=n;i++){
        for (int j=0;j<=n;j++){
            for (int k=0;k<=200;k++){
                if (j<n){
                    if (k+a[i].val>200){
                        if (k+a[i].val>=0)
                            dp[i][j+1][200]+=dp[i-1][j][k]*a[i].p*0.01;
                    }else {
                        if (k+a[i].val>=0)
                            dp[i][j+1][k+a[i].val]+=dp[i-1][j][k]*a[i].p*0.01;
                    }
                }
                dp[i][j][k]+=dp[i-1][j][k]*(100-a[i].p)*0.01;
            }
        }
    }
    double ans=0;
    for (int j=l;j<=n;j++){
        for (int k=0;k<=200;k++){

            ans+=dp[n][j][k];
        }
    }
    printf("%.12lf",ans);
    return 0;
}

P2057 [SHOI2007] 善意的投票 / [JLOI2010] 冠军调查
一道经典的最小割模型 ,详细证明看题解

先分阵营连边,再两两朋友连双向边,跑最大流即可

int main(){
    cin>>n>>m;
    s=n+1;t=n+2;
    for (int i=1;i<=n;i++){
        int x;
        cin>>x;
        if (x)add(s,i,1);
        else add(i,t,1);
    }
    for (int i=1;i<=m;i++){
        int x,y;
        cin>>x>>y;
        add(x,y,1);
        add(y,x,1);
    }
    cout<<dinic();
    return 0;
}

一道图上LIS(CF960F Pathwalks)主席树优化方法待补;
一道状压dpCF417D Cunning Gena;
一道概率dpCF167B Wizards and Huge Prize;
一道排列组合CF893E Counting Arrays;
一道最小割的经典模型P2057 [SHOI2007] 善意的投票 / [JLOI2010] 冠军调查

今天dp的种类很多,状态设计和转移都比较简单,但是一些细节和处理比较巧妙,例如概率dp的先排序再dp,边界范围出了几次问题;写状压dp的时候漏掉了当前点不选的转移;补了一下排列组合的插板法公式;复习了一点基础的线段树;了解了一下最小割的经典模型

明日计划:尽可能多做几道dp,下午5点打一场cf,补cf的题,然后继续做dp,有时间可以把网络流最小割的变式题做了,或者继续学习线段树

你可能感兴趣的:(算法)