Educational Codeforces Round 87

https://codeforces.com/contest/1354

水题略过,从C开始。

 1354C 1354D 1354E 1354F

C2. Not So Simple Polygon Embedding

记 A 为边对中心的角度的一半,即 0.5 对应角度

A= pi/(2*n)

以六边形为例,简单套个矩形结果是长方形: 1/tan(A)  &  1/sin(A)

但是我们要的是正方形,所以需要转动角度 B (由于对称性可以设 0<=B<=A/2 )

如果猜对称,可以猜出 B=A/2,但不确定的话就只好算算了

1/tan(A)  ->  cos(A-B)/sin(A)

1/sin(A)  ->  cos(B)/sin(A)

发现只有 B=A/2这一个解,边长 cos(A/2)/sin(A) = 0.5/sin(A/2)

    #include<bits/stdc++.h>
     
    using namespace std;
     
    const double pi=acos(-1.0);
     
    int main() {int t;
        cin>>t;
        while(t--) {
            int n;
            scanf("%d",&n);
            printf("%.8lf\n",0.5/sin(pi/(4*n)));
        }
    }

D. Multiset

很坑输入时间,注意scanf起步,getchar更好

可以用BIT 纪录 1-n 内每个数的个数,在BIT O(log(n)) 区间和的基础上,补充一个二分查找可以O(log2(n))找到第K数,这样就可以过了

但使用BIT把O(log2(n))变成O(log(n))也是可以的。

注意一般的二分查找,每查一步,求新的区间和需要 O(log(n))

但是BIT有个特点,对于p>q>=0, d[2^p+2^q] 存储了 [2^p+1,2^p+2^q] 区间的和

如果你上一步查过 1到2^p的区间和, 现在查 1到(2^p+2^q) 其实只需要把之前的结果加上 d[2^p+2^q]就行

#include<bits/stdc++.h>

using namespace std;

template<typename T>
class BItree {
public:
    unsigned sz;
    T* d;
    BItree(unsigned n) {
        sz=n+1;
        d=new T[sz] {};
    }
    ~BItree(){
        delete[] d;
    }
    inline void add(unsigned p,T num) {
        while(p<sz) {
            d[p]+=num;
            p+=p&(-p);
        }
    }
    inline T getsum(unsigned p) {
        T sum=0;
        while(p) {
            sum+=d[p];
            p&=p-1;
        }
        return sum;
    }
};

int main() {
    int n,q,m,x;
    scanf("%d%d",&n,&q);
    for(m=1;m*2<=n;m*=2);
    BItree<int> a(n+1);
    for(int i=0;i<n;++i){
        scanf("%d",&x);
        a.add(x,1);
    }
    for(int i=0;i<q;++i){
        scanf("%d",&x);
        if(x>0) a.add(x,1);
        else{
            x=-x;
            int ans=0;
            for(int step=m;step;step>>=1){
                if(ans+step<=n&&a.d[ans+step]<x){
                    x-=a.d[ans+step];
                    ans+=step;
                }
            }
            a.add(ans+1,-1);
        }
    }
    for(int i=1;i<=n;++i){
        if(a.d[i]){
            cout<<i<<endl;
            return 0;
        }
    }
    cout<<0<<endl;
}

 

E. Graph Coloring

很容易发现,可以无视 label 3 与 label 1 的区别,最后(如果)需要输出节点label 的时候,把前n3个'1' 改成 '3' 就行。

 

先考虑一个连通块blocks[bi],有如下3 种情况:

1. 没有办法

直接NO

2. 把序号最小的节点标为 1, 然后一路用212...标完

一边标一边计数,有 blocks[bi][1] 个节点表了 1, blocks[bi][2] 个标了 2.

3. 把序号最小的节点标为 2, 然后一路用121...标完

刚好与2.的情况相反,无需再算

 

现在我们对每个联通块得到了结论:要么在这里标 blocks[bi][1] 个 '1', 要么标 blocks[bi][2] 个 ’1‘

又,一共要有 n1+n3 个 ’1‘

具体解决可以看代码吧

#include<bits/stdc++.h>

using namespace std;

const int N=5004,M=100004;
vector<int> nbr[N];
int value[N]={};
bool fail=false;
int blocks[N][3]={},bi=0,block_of[N];
int bflip[N];

void grow_block(int id,int v){
    //cout<<id<<" "<<v<<"\t";
    if(fail) return;
    value[id]=v;
    ++blocks[bi][v];
    block_of[id]=bi;
    for(int i=0;i<nbr[id].size();++i){
        if(value[nbr[id][i]]==0){
            grow_block(nbr[id][i],v^3);
        }else if(value[nbr[id][i]]==v){
            //cout<<"FAIL at "<<id<<" "<<nbr[id][i]<<endl;
            fail=true;
            return;
        }
    }
}

bool get_block_choice(int i,int left){
    static char mem[N][N];
    if(left<0) return false;
    if(i==bi) return left==0;
    if(mem[i][left]) return mem[i][left]=='Y';
    if(get_block_choice(i+1,left-blocks[i][1])){
        bflip[i]=0;
        mem[i][left]='Y';
    }else if(get_block_choice(i+1,left-blocks[i][2])){
        bflip[i]=3;
        mem[i][left]='Y';
    }else{
        mem[i][left]='N';
        return false;
    }
    return true;
}

int main() {
    int n,n1,n2,n3,m;
    int a,b;
    scanf("%d%d%d%d%d",&n,&m,&n1,&n2,&n3);
    for(int i=0;i<m;++i){
        scanf("%d%d",&a,&b);
        nbr[a].push_back(b);
        nbr[b].push_back(a);
    }
    /*for(int i=1;i<=n;++i){
        cout<<i<<": ";
        for(int j=0;j<nbr_size[i];++j){
            cout<<nbr[i][j]<<" ";
        }
        cout<<endl;
    }*/
    for(int i=1;i<=n;++i){
        if(value[i]) continue;
        //cout<<"\nblock "<<bi<<":"<<endl;
        grow_block(i,1);
        //cout<<"\nNode 1: "<<blocks[bi][1]<<"\tNode 2: "<<blocks[bi][2]<<endl;
        ++bi;
        if(fail){
            puts("NO");
            return 0;
        }
    }
    if(!get_block_choice(0,n1+n3)){
        puts("NO");
        return 0;
    }
    puts("YES");
    for(int i=1;i<=n;++i){
        int out=(value[i]^bflip[block_of[i]])+'0';
        if(out=='1'&&n3){
            --n3;
            out='3';
        }
        putchar(out);
    }
    puts("");
}

 F. Summoning Minions

分为两组mimions:

1. 保留至最后, k个位置, 第i个贡献 a+b(i-1)

2. 上场退场buff机 贡献 b(k-1)

注意1中的b是降序,所以可以排序+DP

#include <bits/stdc++.h>

using namespace std;

struct Mn {
    int b,a,id;
} mn[100];

int dp[80][80];

int main() {
    ios::sync_with_stdio(false);
    int T;
    cin>>T;
    while(T--) {
        int n,k;
        cin>>n>>k;
        for(int i=1; i<=n; ++i) {
            cin>>mn[i].a>>mn[i].b;
            mn[i].id=i;
        }
        sort(mn+1,mn+n+1,[](const Mn& x,const Mn& y) {
            return x.b<y.b;
        });
        for(int i=0; i<=n; ++i) {
            for(int j=0; j<=k; ++j) {
                dp[i][j]=-1000000000;
            }
        }
        dp[0][0]=0;
        for(int i=1; i<=n; ++i) {
            int a=mn[i].a, b=mn[i].b;
            dp[i][0]=dp[i-1][0]+(k-1)*b;
            for(int j=1; j<=k&&j<=i; ++j) {
                dp[i][j]=max(dp[i-1][j]+(k-1)*b, dp[i-1][j-1]+a+b*(j-1));
            }
        }
        vector<int> ans1,ans2;
        for(int x=n,y=k; x>0; --x) {
            if(y>0&&dp[x][y]==dp[x-1][y-1]+mn[x].a+mn[x].b*(y-1)) {
                --y;
                ans1.push_back(mn[x].id);
            } else {
                ans2.push_back(mn[x].id);
            }
        }
        cout<<ans1.size()+2*ans2.size()<<endl;
        for(int i=ans1.size()-1; i>0; --i) cout<<ans1[i]<<" ";
        for(int i=ans2.size()-1; i>=0; --i) cout<<ans2[i]<<" "<<-ans2[i]<<" ";
        cout<<ans1[0]<<endl;
    }
    return 0;
}

 

posted @ 2020-05-17 22:14  141421356  阅读(172)  评论(0)    收藏  举报