8VC Venture Cup 2017 - Elimination Round (D、F)


D. PolandBall and Polygon

题意:

给定n,表示有一个正n边形,
给定一个整数k,保证k和n互质
第一个点的位置为1,第一次操作你要在点1到点1+k之间画一条线
第二次 操作你要在1+k到1+2k之间画一条线,如果1+2k>n,则改为与1+2k-n连线(因为多边形是一个环)

不断进行操作直到点x与点x+k已经有边的时候停止

问每次操作之后,多边形被划分为多少个图形

数据范围:n<=1e6,2<=k<=n-2,gcd(k,n)=1

例如n=5,k=2时,前两次画线操作依次为:
在这里插入图片描述
五边形被划分为两部分
在这里插入图片描述
五边形被划分为3部分

共画线5次,将五边形划分的个数分别为2,3,5,8,11

解法:

因为gcd(n,k)=1,所以肯定是画n次线
容易发下每次增加的块的数量为连接的两点之间(不包含这两点)直线的数量+1,如下图中一次画线,增加了3:
在这里插入图片描述

如何统计两点间有多少条直线呢?其实就是两点之间(不包括这两点)的点的度数和(n边形最外面的初始边不计度数)
例如上图中,下面那个图形要连接2到4,中间顶点3的度数为2,因此增加2+1=3个块

可以将n个顶点的环转化为一个区间,计算区间度数和用树状数组搞搞就行了

这题有一个坑点就是当k>n-k的时候,k要改为n-k,否则区间会错,手画一下n=5,k=4的情况就知道为什么了
因为画线的时候线的左右两端是两个区间,应该要取小区间,但是k>n-k的情况会取大区间,因此要改k

还有就是要开longlong

code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e6+5;
struct BIT{
    int c[maxm];
    int lowbit(int i){
        return i&-i;
    }
    void add(int i,int t){
        while(i<maxm){
            c[i]+=t;
            i+=lowbit(i);
        }
    }
    int ask(int i){
        int ans=0;
        while(i){
            ans+=c[i];
            i-=lowbit(i);
        }
        return ans;
    }
}T;
signed main(){
    int n,k;cin>>n>>k;
    k=min(k,n-k);//注意要取min
    int x=1;
    vector<int>ans;
    int now=1;
    for(int i=1;i<=n;i++){
        int y=x+k;
        if(y>n)y-=n;
        T.add(y,1);//degree
        T.add(x,1);
        if(x<y){//[x+1,y]
            int cnt=T.ask(y-1)-T.ask(x);
            now+=cnt+1;
        }else{//[x+1,n]+[1,y-1]
            int cnt=T.ask(n)-T.ask(x)+T.ask(y-1);
            now+=cnt+1;
        }
        ans.push_back(now);
        x=y;
    }
    for(int v:ans){
        cout<<v<<' ';
    }
    return 0;
}

F. PolandBall and Gifts

题意:

给一个长度为n的排列p,p(i)!=i,第i个人会送礼物给p(i)
但是现在已知有k个人忘记带礼物了,如果i忘记带礼物,那么i和p(i)都不能收到礼物
问无法收到礼物的人最少和最多有多少个

数据范围:n<=1e6,k<=n

解法:

先将排列化为若干有向环
先考虑如何计算最大值:
在环中,一个人忘记带礼物,这个人和他指向的人都没有礼物
因此每隔一个点忘带一个显然最优,假设环的长度为v,
如果是偶环,那么v/2个人忘记带就有v个人没有礼物
如果是奇环,那么v/2+1个人忘记带才有v个人没有礼物,也就是说多出来的那个人要单独花费一次
贪心一下就行了,优先找一个人忘带贡献为2的,最后再找一个人忘带贡献为1的

然后考虑如何计算最小值:
显然k个人忘带,答案肯定至少为k
我们已知环中相隔一个点忘带一个是最大值,容易想到相邻点都忘带就是最小值
假设一个环的长度为v,如果k>=v,那么这v个人全部没有礼物,否则v中的k+1个人没有礼物
因为k个相邻的人忘带礼物,不能充满 环的话就是一条链,最后会额外拖累一个人没有礼物
如果存在若干个环,这些环的长度等于k,那么答案就是k,否则答案为k+1
选出的环全充满则答案为k比较显然,不充满答案为k+1是因为只有一个环没充满,没充满的话会多出一个人
现在问题变为判断是否存在一种方案,使得选出的环长度恰好和为k,是一个背包计数问题
考虑到数据范围很大,n最大1e6,01背包O(kn)的复杂度会炸
可以先将长度相同的环合并,变为多重背包问题,再进行二进制拆分优化,复杂度变为O(klogn)

code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=1e6+5;
int mark[maxm];
int cc[maxm];
int p[maxm];
int n,k;
void cal(int x){//找环
    int cnt=0;
    while(!mark[x]){
        mark[x]=1;
        cnt++;
        x=p[x];
    }
    cc[++cc[0]]=cnt;
}
signed main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)scanf("%d",&p[i]);
    for(int i=1;i<=n;i++){
        if(!mark[i]){
            cal(i);
        }
    }
    //计算ma
    int ma=0;
    int kk=k;
    int odd=0;
    for(int i=1;i<=cc[0];i++){
        int v=cc[i]/2;
        if(kk>=v){
            ma+=v*2,kk-=v;
        }else{
            ma+=kk*2,kk=0;
            break;
        }
        if(cc[i]&1)odd++;//奇环多出来一个先留着
    }
    ma+=min(kk,odd);//剩余的kk只能用奇环多出来的补
    //计算mi
    sort(cc+1,cc+1+cc[0]);
    vector<int>temp;
    for(int i=1;i<=cc[0];i++){//合并为多重背包然后二进制拆分为01背包
        int j=i;
        int v=cc[i],cnt=0;
        while(j<=cc[0]&&cc[j]==cc[i]){
            cnt++,j++;
        }
        int p=1;
        while(cnt>=p){//二进制拆分
            temp.push_back(p*v);
            cnt-=p;
            p<<=1;
        }
        if(cnt){
            temp.push_back(cnt*v);
        }
        i=j-1;
    }
    int mi=k+1;
    bitset<maxm>t;
    t[0]=1;
    for(int v:temp){
        t|=(t<<v);
    }
    if(t[k])mi=k;
    printf("%d %d\n",mi,ma);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值