codeforces每日一题31-40

目录:

31.1025D. Recovering BST(区间DP)

32.1187E. Tree Painting(换根DP)

33.23B. Party(思维)

34.1062B.Math(唯一分解性定理)

35.340D. Bubble Sort Graph(DP)

36.922C. Cave Painting(暴力)

37.725D. Contest Balloons(优先队列)

38.167B. Wizards and Huge Prize(概率DP)

39.678C. Joty and Chocolate(GCD)

40.527C. Glass Carving(STL)

1025D. Recovering BST(区间DP)

题意:

给一个数组,问你能否构造出一颗搜索二叉树,使得每条边相邻的两个节点的值不互素

思路:

观察样例可以发现,二叉搜索树从左往右递增,于是我们就想到了区间DP

但是如果仅以DP[i][j]判断区间是否可行的话就无法记录根的信息,如果要再加上一维的话那么空间跟时间复杂度都会炸

那么我们就将其拆分l[i][j]表示以i为根最多能任意向左构建多大的左子树,r[i][j]同理

所以我们只需要在区间DP的第三层循环枚举区间的根并且看看能否往两边拓展就行了

#include<iostream>
#include<algorithm>
 using namespace std;
 const int maxn=705;
 typedef long long ll;
 int  gcd(int a,int b) {return b == 0 ? a : gcd(b, a % b);}
 int e[maxn][maxn],l[maxn][maxn],r[maxn][maxn],a[maxn]; 
 int main()
 {
     int n;
     scanf("%d",&n);
     for(int i=1;i<=n;i++){
         scanf("%d",&a[i]);
         l[i][i]=r[i][i]=1;
     } 
     for(int i=1;i<=n;i++)
         for(int j=1;j<i;j++)
             if(gcd(a[i],a[j])>1) e[i][j]=e[j][i]=1;
     for(int len=1;len<=n;len++){
         for(int i=1,j;(j=i+len-1)<=n;i++){
             for(int k=i;k<=j;k++){
                 if(l[i][k]&&r[k][j]){
                     if(len==n){
                         cout<<"Yes";
                         return 0;
                     }
                     r[i-1][j]|=e[i-1][k];
                     l[i][j+1]|=e[k][j+1];
                 }
             }
         }
     }
     cout<<"No";
     return 0;
 }
View Code

1187E. Tree Painting(换根DP)

题意:

给一颗无根树,一开始对一个点进行染色,然后答案加上其所在连通块的大小,之后每次可以对所有跟黑点相连的白点进行染色,加上其所在连通块的大小,直到整棵树为黑,求答案的最大值

思路:

可以发现只要选定了根节点答案就会确定,我们先进行第一次DFS求出每个点包的子树大小,再通过累加每个子树的大小求出以1位根时的答案

之后进行换根DP,每次换根时:dp[v] = dp[x] - 2 * siz[v] + siz[1] 其中x为v的父亲

因为当v成为新的根时,就会失去将V染成联通块时的答案(可以理解为,如果v为对答案的贡献就整棵树的大小,不为根就为其子树的大小),还会多出来以x的子树的大小(其大小就为siz[1] - siz[v])

#include<iostream>
#include<algorithm>
#include<vector>
 using namespace std;
 typedef long long ll;
 const int maxn=2e5+10;
 ll siz[maxn],dp[maxn],ans=0;
 vector<int> a[maxn];
 void dfs1(int x,int fa)
 {
     siz[x]=1;
     for(int i=0;i<a[x].size();i++){
         int v=a[x][i];
         if(v==fa) continue;
         dfs1(v,x);
         siz[x]+=siz[v];
     }
 }
 void dfs2(int x,int fa)
 {
     for(int i=0;i<a[x].size();i++){
         int v=a[x][i];
         if(v==fa) continue;
         dp[v]=dp[x]-2*siz[v]+siz[1];
         ans=max(ans,dp[v]);
         dfs2(v,x);
     }
 }
 int main()
 {
     int n,u,v;
     scanf("%d",&n);
     for(int i=1;i<n;i++){
         scanf("%d%d",&u,&v);
         a[u].push_back(v);
         a[v].push_back(u);
     }
     dfs1(1,1);
     for(int i=1;i<=n;i++) dp[1]+=siz[i];
     dfs2(1,1);
     cout<<ans;
     return 0;
 }
View Code

23B. Party(思维) 

题意:

有n个人参加聚会,第一次没有朋友的人离开,第二次只有一个朋友的人离开....最后有n-1个朋友的人离开,问最后能有多少人留下

思路:

我们所有人描绘成n个点,朋友关系描绘成两点有边相连,那么这样就会变成与一个类似于拓扑排序的东西

画图可以知道,当关系图成一个链状时会是最优的,每次只会使得链两头的人离开,其他人留下,所以答案就为n-2

#include<iostream>
#include<algorithm>
 using namespace std;
 int main()
 {
     int t,n;
     scanf("%d",&t);
     while(t--){
         scanf("%d",&n);
         if(n<3) cout<<0<<endl;
         else cout<<n-2<<endl;
     }
    return 0;
 }
View Code

1062B.Math(唯一分解性定理)

题意:

给一个数n,你可以将其乘上任何数,并且对其开方(如果结果为整数),问你可以将其最小变成多少,需要进行几次操作

思路:

先将n分解成为P1a1·P2a2....的形式,那么最可以变成的最小的数就为P1*P2*....

对于每一个ai,都有2ui  ≤  2ai  ≤ 2ui-1 ,现在我们令U=max(Ui) ,如果对于每个ai都等于U的话,那么答案就为U(开U次方即可)

如果有一个ai 不等于 U,那么答案就为U+1(因为可以乘上一个数使得所有的ai=U)

#include<iostream>
#include<algorithm>
#include<map>
 using namespace std;
 map<int,int> m;
 int main()
 {
     int n,res=1,cnt=0,mx=0;
     scanf("%d",&n);
     for(int i=2;i<=n;i++){
         if(n%i==0){
             res*=i;
             while(n%i==0){
                 m[i]++;
                 n/=i;
             }
            mx=max(mx,m[i]);
         }
     }
    while((1<<cnt)<mx) cnt++;
    mx=(1<<cnt);
    for(map<int,int> ::iterator it=m.begin();it!=m.end();it++){
        if(it->second!=mx){
            cnt++;
            break;
        } 
    }
    cout<<res<<" "<<cnt;
  } 
View Code

340D. Bubble Sort Graph(DP)

题意:

给一个数组,然后进行冒泡排序,每次冒泡排序交换时对应的两个数在图中的点连接一条边,求在排序后的最大独立点集(在一个集合中,所有的点之间没有连边)

思路:

首先我们可以发现:对于一个数a, 其会与所有的(a> a并且i < j) 连上一条边

那么对应的最大独立点集个数就是最长上升子序列的长度

#include<iostream>
#include<algorithm>
#define lowbit(x) x&(-x) 
 using namespace std;
 const int maxn=1e5+10;
 int c[maxn],a[maxn],n,dp[maxn];
 void add(int x,int val)
 {
     while(x<=n){
         c[x]=max(val,c[x]);
         x+=lowbit(x);
     }
 }
 int sum(int x)
 {
     int ret=0;
     while(x>=1){
         ret=max(ret,c[x]);
         x-=lowbit(x);
     }
    return ret;
 }
 int main()
 {
     int ans=0;
     scanf("%d",&n);
     for(int i=1;i<=n;i++) scanf("%d",&a[i]);
     for(int i=1;i<=n;i++){
         dp[i]=sum(a[i])+1;
         add(a[i],dp[i]);
         ans=max(ans,dp[i]);
     }
    cout<<ans;
    return 0;
 }
View Code

922C. Cave Painting(暴力)

题意:

给两个整数n,k,问是否n%i (1≤ i ≤ k)的余数都不相同

思路:

可以看到 n%1=0 ,那么就要求n%2=1 ...... 最后得出满足条件的式子为n%i=i-1

满足 n%=1 的数有 1, 3, 5, 7, 9, … 在此基础上满足 n%3=2 的数,就只有 5, 11, 17, …了,容易发现剩下的数仍然是等差数列,但公差扩大了3倍。同样的,在此基础上,再满足 n%4=3 的数就只有 11, 23, 35, …了。的这个速度都接近阶乘了,而且首项增长的也是飞快,所以要让nn 对 1∼k取模的每个余数都满足条件,那n 差不多至少应该是 k!级别的。反过来说,即使是1018,它能满足的k也不可能很大,因此就直接暴力找就行了

#include<iostream>
#include<algorithm>
#include<set>
 using namespace std;
 typedef long long ll;
 set<ll> s;
 int main()
 {
     ll n,k;
     scanf("%lld%lld",&n,&k);
     if(n%2==0){
         if(k>1) cout<<"No";
         else cout<<"Yes";
     }
    else{
        for(int i=1;i<=k;i++){
            ll x=n%i;
            if(s.count(x)){
                cout<<"No";
                return 0;
            }
            else s.insert(x); 
        }
        cout<<"Yes";
    }
    return 0;
  } 
View Code

725D. Contest Balloons(优先队列)

题意:

每只队有体重值和气球数量,如果一支队的气球数量大于体重,这支队就会不计排名,一支队的排名就为有多少只气球比他多的队伍数加一,现在你可以将气球分发给其他队伍,问你们队可以达到的最好排名是多少

思路:

可以想到的是想排名变好你就得将气球给前面多的队伍,让其不计排名,因此贪心的做法就是将气球数比我们队伍多的队扔到一个优先队列中,以(Wi - Ti +1 )作为排序依据,也就是使其不计排名要花费的最少气球数,越少越前面,每次循环如果剩余的气球够多的话就使队列头部的队伍不计排名

但是,随着我们自己气球的减少,我们有可能被后面的队伍反超,因此在每次给气球操作时,我们还要将原本排名在我们之后的但是已经超过我们的队伍加到队列中,直到队列为空,或者剩余的气球数无法再使得任何队伍不计排名,并且在这个过程中动态记录最优排名

#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
 using namespace std;
 typedef long long ll;
 struct Node{
    Node(ll a,ll b){
        t=a;
        w=b;
    }
    ll t, w;
};
int cmp(Node a,Node b){return a.t>b.t;}
priority_queue<ll,vector<ll>,greater<ll> > p;
vector<Node> a;
 int main()
 {
     ll t,w,n,t1,t2;
     scanf("%lld%lld%lld",&n,&t,&w);
     for(int i=2;i<=n;i++){
         scanf("%lld%lld",&t1,&t2);
         if(t1>t) p.push(t2-t1+1);
         else a.push_back(Node(t1,t2));
     }
    int h=0,ans; 
    ans=p.size()+1;
    sort(a.begin(),a.end(),cmp);
    while(!p.empty()){
        ll x=p.top();
        if(x<=t){
            t-=x;
            p.pop();
        }
        else break;
        for(;h<a.size();h++){
            if(a[h].t>t) p.push(a[h].w-a[h].t+1);
            else break;
        }
        ans=min(ans,(int)p.size()+1);
    }
    cout<<ans;
    return 0;    
  } 
View Code

167B. Wizards and Huge Prize(概率DP)

题意:

有n场比赛,每场比赛都有获胜的概率并且可以赢得奖品并且刚开始时你有一个容量为k的背包,每个奖品要么可以占背包一体积要么可以扩容你的背包,问你最少赢l场,并且可以把礼物全部带走的概率是多少

思路:

设dp[i][j][k]为前i场比赛赢j场时背包剩余容量为k的概率,但是由于比赛的顺序可以改变,也就意味着有时候背包容量可以为负数,只要当i=n时为正即可

由于数组下标不能为负,所以我们将k整体偏移200,只要最后最后大于200就行

转移方程为:dp[i+1][j][k]+=dp[i][j][k]*(1.0-p[i+1])

      dp[i+1][j+1][t]+=dp[i][j][k]*p[i+1]

#include<iostream>
#include<algorithm>
 using namespace std;
 const int maxn=205;
 int a[maxn];
 double  dp[maxn][maxn][405],p[maxn];
 int main()
 {
     int n,l,s,x;
     scanf("%d%d%d",&n,&l,&s);
     for(int i=1;i<=n;i++){
         scanf("%d",&x);
         p[i]=1.0*x/100;
     }
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    dp[0][0][s+200]=1;
    for(int i=0;i<n;i++){
        for(int j=0;j<=i;j++){
            for(int k=0;k<=400;k++){
                int t=min(400,k+a[i+1]);
                dp[i+1][j][k]+=dp[i][j][k]*(1.0-p[i+1]);
                if(t>=0) dp[i+1][j+1][t]+=dp[i][j][k]*p[i+1]; 
            }
        }
    }
    double ans=0;
    for(int i=l;i<=n;i++)
        for(int j=200;j<=400;j++)
            ans+=dp[n][i][j];
    printf("%.12lf",ans);
    return 0; 
 }
View Code

678C. Joty and Chocolate(GCD)

题意:

一条长度为n的直线,坐标为a的倍数的地方可以染成红色,b的倍数的地方染成蓝色,同为a,b倍数的地方可以染成你想要的颜色,染成红色可以获得q个巧克力,蓝色p个巧克力,问你最多能获得多少巧克力

思路:

很明显,公倍数的地方染成能获得更多巧克力的颜色即可

#include<iostream>
#include<algorithm>
 using namespace std;
 typedef long long ll;
 ll gcd(ll a,ll b){return (b==0)?a:gcd(b,a%b);}
 int main()
 {
     ll n,a,b,p,q,ans=0;
     cin>>n>>a>>b>>p>>q;
     ans+=n/a*p+n/b*q;
     ll x=n/(a*b/gcd(a,b));
     ans-=p>=q?x*q:x*p;
     cout<<ans;
     return 0;
  } 
View Code

527C. Glass Carving(STL)

题意:

有一块矩形木板,每次可以水平或者竖直切一刀,输出每次切割后小矩形面积的最大值

思路:

可以发现,水平跟竖直是互不影响的,所以我们分别维护水平每段的最大值跟竖直每段的最大值相乘就为答案

分别用两个set跟multiset维护坐标跟每段的长度,切割时,利用二分查找在set中查找切割位置是在哪块木板,之后插入坐标

并且在multiset删除原来木板的长度,再插入切割后两块木板的长度,主义multiset删除要用地址删除法

#include<iostream>
#include<map>
#include<algorithm>
#include<set>
 using namespace std;
 typedef long long ll;
 set<int> m1,m2;
 multiset<int,greater<int> > s1,s2;
 int main()
 {
     int w,h,n,mx,my,x;
     char op[3];
     scanf("%d%d%d",&w,&h,&n);
     m1.insert(w),m2.insert(h);
     m1.insert(0),m2.insert(0);
     s1.insert(w),s2.insert(h);
     set<int> ::iterator it1,it2;
     for(int i=1;i<=n;i++){
         scanf("%s%d",op,&x);
         if(op[0]=='H'){
             it1=m2.lower_bound(x);
             it2=it1;
             it2--;
             int dis=*it1-*it2;
             m2.insert(x);
             s2.erase(s2.find(dis));
             s2.insert(x-*it2);
             s2.insert(*it1-x);
             cout<<(ll)(*s1.begin())*(ll)(*s2.begin())<<endl;
        }
        else{
             it1=m1.lower_bound(x);
             it2=it1;
             it2--;
             int dis=*it1-*it2;
             m1.insert(x);
             s1.erase(s1.find(dis));
             s1.insert(x-*it2);
             s1.insert(*it1-x);
             cout<<(ll)(*s1.begin())*(ll)(*s2.begin())<<endl;
        }
     }
    return 0;
  } 
View Code

 

posted @ 2020-03-12 23:43  overrate_wsj  阅读(183)  评论(0编辑  收藏  举报