contest 15

比赛链接

比赛中解决的问题

A

Problem description

给定两个一次函数,求相等时最小的值

Data Limit:n <= 100  Time Limit: 1s

Solution

暴力求出所有符合的数,或者用数学整除直接算出,注意小于的情况

Code

if(d>b){swap(b,d),swap(a,c);}
    for(int i=0;i<=500000;i++)
        if((a*i+b-d==0)||(a*i+b-d)%c==0)
        {cout<<a*i+b<<"\n";return 0;}
        cout<<"-1\n";

B

Problem description

给定一个数列,求之中是否存在至少一对相反数

Data Limit:n <= 1e4  Time Limit: 2s

Solution

我们用两个数组分别记录positive 和negative ,注意要两个数组,一个会出乱子,然后看这个位置对应的数组是不是1。注意memset

Code

for(int i=1;i<=m;i++)
    {
        int ok=0,k,t1;
        memset(book1,0,sizeof(book1));
        memset(book2,0,sizeof(book2));
        cin>>k;
        for(int i=1;i<=k;i++)
        {
            cin>>t1;
            if(t1<0) book1[0-t1]=1;
            else book2[t1]=1;
        }
        for(int i=1;i<=20000;i++)
            if(book1[i]&book2[i])ok=1;
        if(!ok) {puts("YES");return 0;}
    }
    puts("NO");

赛后补题

C

Problem description

给定两个集合,每一个集合对应的是该人可以移动的步数,怪物起始可以在任意点,到达1就获胜,两个人都足够聪明,求怪物起始以及谁先手的胜负情况

Data Limit:n <= 1e4  Time Limit: 4s

Solution

我们用记忆化搜索,当且仅当一个点的所有情况是必败态,他的后继是必胜态,于是我们枚举所有的情况就可以了

Code

void dfs(int now,int p)
{
    if(f[p][now]) return ;
    f[p][now]=2;
    if(now==0) return ;
    for(int i=0;i<k[!p];i++)
    {
        int lat=(now-v[!p][i]+n)%n;
        Orz_He_Xv[!p][lat]++;
        if(Orz_He_Xv[!p][lat]==k[!p])
        {
            f[!p][lat]=1;
            for(int kk=0;kk<k[p];kk++)
                dfs((lat-v[p][kk]+n)%n,p);
        }
    }
}

D

Problem description

给定一个图,有三类边 u->v u->[l,r],[l,r]->v每一条边都有权值,求单源最短路

Data Limit:n <= 1e5  Time Limit: 2s

Solution

首先我们要处理区间边的情况,我们建造两颗线段树(有shenben说一颗也可以),然后对应线段树上的区间连边,比如你要连的区间为[1,3]那么你分别向[1,2] [3,3]连边,然后跑一边dij+堆的最短路就可以了

Code

void build(int l,int r,int rt)
{
    if(l==r)
    {
        tr[rt][0]=tr[rt][1]=l;
        return ;
    }
    tr[rt][0]=++tot;
    tr[rt][1]=++tot;
    build(l,mid,ls),build(mid+1,r,rs);
    add(tr[rt][0],tr[ls][0],0),add(tr[rt][0],tr[rs][0],0);
    add(tr[ls][1],tr[rt][1],0),add(tr[rs][1],tr[rt][1],0);
}

void update(int l,int r,int rt,int from,int L,int R,int W,int t)
{
    if(L<=l&&r<=R)
    {
        if(t==2) add(from,tr[rt][0],W);
        else if(t==3) add(tr[rt][1],from,W);
        return ;
    }
    if(L<=mid) update(l,mid,ls,from,L,R,W,t);
    if(R>mid) update(mid+1,r,rs,from,L,R,W,t);
    return ;
}

void dij()
{
    priority_queue<Node> q;
    memset(dis,38,sizeof(dis));
    inf =dis[0];
    dis[s]=0;
    q.push((Node){s,0});
    while(!q.empty())
    {
        Node x=q.top();q.pop();
        if(book[x.p]) continue;
        book[x.p]=1;
        for(int i=head[x.p];i;i=e[i].nxt)
        {
            if(dis[e[i].to]>x.w+e[i].w)
            {
                dis[e[i].to]=x.w+e[i].w;
                q.push((Node){e[i].to,dis[e[i].to]});
            }
        }
    }
}

Ps: dij的continue的部分我写成了return 然后被坑了好久

E

Problem description

给定颜色序列,求分成最小的连续区间,使得区间内的元素的颜色的种类小于k(1<=k<=n)

Data Limit:n <= 1e5  Time Limit: 2s

Solution

我的是暴力做法,我们通过实践可以发现,如果存在一个l,r,他们的答案是相同的,那么他们之间的答案也是相同的,我们就可以进行二分区间然后暴力求解啦//
正解貌似是主席树(???)//
-----------------------------

update

这里再说一种线段树的方法,我们用线段树的叶节点维护一段1~n的区间,表示这里的颜色的数目,然后我们在树上二分//
初始化:我们用一个vector数组记录这个节点可以用的k的值//
二分每一次贪心,如果可以走右边尽量走右边//
建树:存最小值,至于为什么下面会证明//
------------
反证法,如果存最大值得话,当一个值等于左子树的时候我们是不能区分到底是向左还是向右的,因为有可能右边的最左边也有可能和他相等,//
但是我们用贪心的思想的话这样值不满足的。//
证毕

### 在Code的下面会有第二个代码的完整实现

update

好像还有一种树状数组的方法??

update

线段树的做法其实和树状数组的思想是一样的,但是树状数组常数比较小,并且空间小,所以比较优//
简单分析一下,就是把线段树的部分替换成了树状数组。//
不同之处:二分的时候不同,线段树是建立一些虚拟的节点以便于树上二分,而树状数组的定义就是一部分的前缀和,所以可以直接二分。//
更新不同,用拆分的思想更新//
注意点,二分是二分二进制上的0和1,然后每一次二分的点是右区间的左端点。//
总而言之,线段树的思想还是非常巧妙的啦//
--------

由于不能换行,于是我用//表示换行了,这样思路清晰一些

Code

暴力出奇迹
inline int get(int x)
{
    memset(col,0,sizeof(col));
    int cnt=0,ans=1;
    for(int i=1;i<=n;i++)
    {
        if(col[a[i]]==ans) continue;
        cnt++;
        col[a[i]]=ans;
        if(cnt>x) {ans++;cnt=1;col[a[i]]=ans;}
    }
    return ans;
}

void solve(int l,int r)
{
    if(l>r) return ;
    ans[l]=get(l),ans[r]=get(r);
    if(ans[l]==ans[r])
    {
        for(int i=l+1;i<=r-1;i++)
            ans[i]=ans[l];
        return ; 
    }
    int mid=l+(r-l)/2;
    solve(l+1,mid),solve(mid,r-1);
    return ;
}
线段树
#include<bits/stdc++.h>
#define next NxTe
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid (l+(r-l)/2)
#define maxn 200000
using namespace std;

int tag[maxn<<4],tr[maxn<<4],a[maxn],head[maxn],next[maxn],ans[maxn];
int n,m;
vector<int> v[maxn];

void pushdown(int rt)
{
    tag[ls]+=tag[rt],tag[rs]+=tag[rt];
    tr[ls]+=tag[rt],tr[rs]+=tag[rt];
    tag[rt]=0;
    return ;
}

void update(int l,int r,int rt,int L,int R,int C)
{
    if(L<=l&&r<=R)
        {tr[rt]+=C,tag[rt]+=C;return ;}
    if(tag[rt]) pushdown(rt);
    if(L<=mid) update(l,mid,ls,L,R,C);
    if(R>mid) update(mid+1,r,rs,L,R,C);
    tr[rt]=min(tr[ls],tr[rs]);
    return ;
}

int find(int l,int r,int rt,int x)
{
    if(l==r) return l;//返回的是地址 
    if(tag[rt]) pushdown(rt);
    if(tr[rs]<=x) return find(mid+1,r,rs,x);//右子树满足先选右子树咯 
    else return find(l,mid,ls,x);
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),head[i]=next[i]=n+1,v[1].push_back(i);
    for(int i=n;i>=1;i--)
        next[i]=head[a[i]],head[a[i]]=i;
    for(int i=1;i<=n;i++)
        if(head[i]<=n)
            update(1,n,1,head[i],n,1);
    int x,y;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<v[i].size();j++)
        {
            x=v[i][j];
            y=find(1,n,1,x)+1;
            v[y].push_back(x);
            ans[x]++;
        }
        update(1,n,1,i,next[i]-1,-1);
    }
    for(int i=1;i<=n;i++)
        printf("%d ",ans[i]);
    
    return 0;
}
树状数组
#include<bits/stdc++.h>
#define lowbit(k) (k&(-k))
#define maxn 200000
using namespace std;
int tr[maxn],next[maxn],head[maxn],ans[maxn],a[maxn];
vector<int> v[maxn];
int n;

void update(int x,int up)
{
    while(x<=n)
    {
        tr[x]+=up;
        x+=lowbit(x);
    }
    return ;
}

int find(int x)
{
    int u=0;
    for(int i=16;i>=0;i--)
    {
        if((u|(1<<i))>n) continue;
        if(tr[u|(1<<i)]<=x)
            u|=(1<<i),x-=tr[u];
    }
    return u;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]),next[i]=head[i]=n+1;
    for(int i=n;i>=1;i--)
        next[i]=head[a[i]],head[a[i]]=i;
    for(int i=1;i<=n;i++)
        if(head[i]<=n)
            update(head[i],1);
    for(int i=1;i<=n;i++)
        v[1].push_back(i);
    
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<v[i].size();j++)
        {
            int x=v[i][j];
            v[find(x)+1].push_back(x);
            ans[x]++;
        }
        update(i,-1),update(next[i],1);
    }
    for(int i=1;i<=n;i++)
        printf("%d ",ans[i]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值