【日记】12.23/【题解】CF Edu78

12.23日记

DP

  1. 洛谷P1280:工作日有N分钟,有K个任务,每个任务从\(p_i\)分钟开始,持续\(t_i\)分钟,每一时刻如果有多个任务要完成,则可以任选一个,但只能干一个工作,问如何选取任务,使得空暇时间最多。数据1e4。

思路:dp[i]表示i-N分钟中,最大的空闲时间。那么从后往前遍历每个任务,这个是无后效性的,与前面的任务怎么选是无关的。那么首先对任务开始时间从大到小排序,再依次从时间n-1开始dp即可。

#include<bits/stdc++.h>
using namespace std;
const int M=1e5+20;
struct T{
    int st,dur;
    bool operator<(const T &x)const{
        return st>x.st;
    }
};
struct Task{
    int n,k,dp[M];
    T a[M];
    void init(){
        scanf("%d%d",&n,&k);
        for(int i=1;i<=k;++i)
            scanf("%d%d",&a[i].st,&a[i].dur);
    }
    void run(){
        sort(a+1,a+k+1);
        int p=1;
        for(int i=n;i>=1;--i)
            if (a[p].st!=i)
                dp[i]=dp[i+1]+1;
            else
                while(a[p].st==i)
                    dp[i]=max(dp[i],dp[i+a[p].dur]),++p;
        printf("%d\n",dp[1]);
    }
}task;
int main(){
    task.init(),task.run();
    return 0;
}

CF Edu78

A. Shuffle Hashing

题意:给字符串s和h,s的hash定义为,将s随机打乱,之后加上一个任意前缀和一个任意后缀,得到的字符串即为s的hash,现询问h是否可以为s的hash。

思路:遍历所有h的长为len(s)的区间,统计各个字符个数,如果和s的各个字符个数一样,那么就是可以。时间复杂度\(O(n^2)\)

#include<bits/stdc++.h>
using namespace std;
const int M=1e2+50;
struct T{
    int ans[28],now[28],len1,len2;
    char s1[M],s2[M];
    void init(){
        for(int i=0;i<26;++i)
            ans[i]=now[i]=0;
        scanf("%s%s",s1,s2);
        len1=strlen(s1),len2=strlen(s2);
    }
    inline bool check(){
        for(int i=0;i<26;++i)
            if (ans[i]!=now[i])
                return false;
        return true;
    }
    void run(){
        init();
        if(len2<len1){
            printf("NO\n");
            return;
        }
        for(int i=0;i<len1;++i)
            ++ans[s1[i]-'a'],++now[s2[i]-'a'];
        int p=len1;
        while(p<len2&&!check())
            --now[s2[p-len1]-'a'],++now[s2[p]-'a'],++p;
        if (check())
            printf("YES\n");
        else
            printf("NO\n");
    }
}t;
int main(){
    int T;
    scanf("%d",&T);
    for(int i=1;i<=T;++i)
        t.run();
    return 0;
}

B. A and B

题意:给两个数a和b,第k次操作为选定一个数,将其加上k。输出使得a和b相等的最少步数。

思路:求出a和b的差d,那么找到第一个x,使得x(x+1)/2>=d(也就是疯狂给那个小的数加),若d!=0,则此时a和b还是不一样。那么问题就转化为了,将x(x+1)/2分成两个数,使其相差d。这就要看x(x+1)/2+d是否为偶数,若为奇数,则还需要继续加,直到为偶数为止,这样才能分成整数。很容易理解一定加了不超过2次。

时间复杂度\(O(\log n)\)

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define mid ((l+r)>>1)
struct T{
    int a,b,d;
    void init(){
        scanf("%d%d",&a,&b);
        d=abs(a-b);
    }
    bool check(int x){
        if (1LL*x*(x+1)/2>=d)
            return true;
        return false;
    }
    void run(){
        init();
        int l=0,r=1e5+20;
        while(l!=r){
            if (check(mid))
                r=mid;
            else
                l=mid+1;
        }
        while((1LL*l*(l+1)/2+d)%2==1)
            ++l;
        printf("%d\n",l);
    }
}t;
int main(){
    int T;
    scanf("%d",&T);
    for(int i=1;i<=T;++i)
        t.run();
    return 0;
}

C.Berry Jam

题意:给2n个罐头,一开始站在n和n+1之间,每个罐头为1或者2,可以选择吃掉i-n和n+1-j两个区间内的所有罐头。问最少需要吃掉多少个罐头,才能使得剩下的1和2的数量相等。

题解:假设1个数>2个数,那么首先计算出要额外吃掉的1的个数,记为x。之后计算的时候碰到1就+1,碰到2就-1。首先计算n往左走,每走过一个就+1/-1。可以想到,如果i处为sum,j处也为sum,而且i<j,那么i一定没用。所以对于每个sum,只需要记录第一个出现的j即可。那么可以将出现的位置存入数组或者队列。对右边也一样处理。之后枚举左0右x,左1右x-1……左x右0,根据记录的位置计算出每种情况要吃的罐头的数量,取最小值即可,注意要处理左边可能没有x的情况。

时间复杂度\(O(n)\)

#include<bits/stdc++.h>
using namespace std;
const int M=1e5+20;
#define tr(x) (x==pd?1:-1)
struct pl{
    int val,num;
    pl(int a=0,int b=0):val(a),num(b){};
};
struct T{
    int n,a[M*2],num[3],pd,dt,sum,ans;
    void init(){
        scanf("%d",&n);
        num[1]=num[2]=0;
        for(int i=1;i<=2*n;++i)
            scanf("%d",&a[i]),++num[a[i]];
        dt=abs(num[1]-num[2]),ans=1e9+7;
    }
    int run(){
        init();
        if (num[1]==num[2])
            return 0;
        if (num[1]>num[2])
            pd=1;
        else
            pd=2;
        deque<pl> l,r;
        sum=0;
        l.push_back(pl(0,0));
        for(int i=n;i>=1;--i){
            sum+=tr(a[i]);
            if (sum>l.back().val)
                l.push_back(pl(sum,n-i+1));
        }
        sum=0;
        r.push_back(pl(0,0));
        for(int i=n+1;i<=2*n;++i){
            sum+=tr(a[i]);
            if (sum>r.back().val)
                r.push_back(pl(sum,i-n));
        }
        while(!l.empty()){
            if (l.front().val>dt)
                break;
            while(r.back().val>dt-l.front().val)
                r.pop_back();
            if (r.back().val+l.front().val==dt)
                ans=min(ans,l.front().num+r.back().num);
            l.pop_front();
        }
        return ans;
    }
}t;
int main(){
    int T;
    scanf("%d",&T);
    for(int i=1;i<=T;++i)
        printf("%d\n",t.run());
    return 0;
}

D. Segment Tree

题意:给n个点,每个点表示一个区间\([l_i,r_i]\),保证所有的l,r均不重复。若两个区间有重叠但不包含,则两个点之间有边。问这个图是否是一颗树。

思路:这也算是一道数据结构题了,结果不会做………………

区间按照l从小到大排序,每读入一个区间就将右端点和编号放入set,那么放入之前,set里剩的元素就是与之有边的元素。如果set里有右端点太小的边就删掉。显然,如果发现了超过了n-1条边,那么就直接NO。如果恰好n-1条边,再判断连通性即可。可以用并查集。

时间复杂度\(O(n\log n)\)

明天再补吧。

猜你喜欢

转载自www.cnblogs.com/diorvh/p/12089254.html
78