A Rooms and Passages

题意

给n个数,从起点出发,一直往右走,遇到一个前面出现过其相反数的正数就停下,问对于每个起点都能走多少步。

分析

  • 倒着递推,如果起点是正数,那么肯定可以走,ans[i]=ans[i+1]+1
  • 如果起点是负数,那就得看这个负数对应绝对值在后面出现的最靠前的位置,而且还要看后面的负数所对应的绝对值的最靠前位置,所以这个lst应该是全局更新的。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+50;
int a[N],n,pos[N],ans[N];
int main(){
//    freopen("in.txt","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    int lst=n+1;
    for(int i=n;i>=1;i--){
        if(a[i]>0){
            ans[i]=ans[i+1]+1;
            pos[a[i]]=i;
        }else{
            if(!pos[-a[i]]){
                ans[i]=ans[i+1]+1;
            }else{
                lst=min(lst,pos[-a[i]]-1);
                ans[i]=lst-i+1;
            }
        }
    }
    for(int i=1;i<=n;i++){
        printf("%d%c",ans[i],i==n?'\n':' ');
    }
    return 0;
}

B Rearrange Columns

题意

有两行字符串,有#.,可以对列进行重排,使得#连在一起。

分析

  • 计算两个#,一个#在上面和在下面的,和没有#的,如果没有两个#而且#有上有下的,就不行。
  • 否则,把两个#的放中间即可。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+55;
char a[2][N];
int main(){
    scanf("%s",a[0]);
    scanf("%s",a[1]);
    int n=strlen(a[0]);
    int shang=0;
    int xia=0;
    int mei=0;
    int liang=0;
    for(int i=0;i<n;i++){
        if(a[0][i]=='#' && a[1][i]=='#'){
            liang++;
        }else if(a[0][i]=='#' && a[1][i]=='.'){
            shang++;
        }else if(a[0][i]=='.' && a[1][i]=='#'){
            xia++;
        }else{
            mei++;
        }
    }
    if(liang || (shang &&!xia) || (xia&&!shang)){
        printf("YES\n");
        for(int i=0;i<mei;i++){
            printf(".");
        }
        for(int i=0;i<shang+liang;i++){
            printf("#");
        }
        for(int i=0;i<xia;i++){
            printf(".");
        }
        printf("\n");
        for(int i=0;i<mei;i++){
            printf(".");
        }
        for(int i=0;i<shang;i++){
            printf(".");
        }
        for(int i=0;i<liang+xia;i++){
            printf("#");
        }
        printf("\n");
    }else{
        printf("NO\n");
    }
    return 0;
}

C Jumps on a Circle

题意

一个环标记为0到p-1,从0开始每次跳1,2,3...步,问跳n步之后会有多少个位置访问过。

分析

跳min(n,2p)次之后肯定会回到原点然后重新跳1,2,3...,所以模拟即可。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e7+50;
ll n,p;
int vis[N];
int main(){
    scanf("%lld%lld",&p,&n);
    vis[0]=1;
    ll now=0;
    for(ll i=1;i<=min(n,2*p);i++){
        now=(now+i)%p;
        vis[now]=1;
    }
    int ans=0;
    for(ll i=0;i<p;i++){
        ans+=vis[i];
    }
    printf("%d\n",ans);
    return 0;
}

D Country Division

题意

给一棵树,每次询问给一些点染成红色,一些点染成蓝色,问能不能删掉一些边使得同色之间能相互到达,不同色之间不能相互到达。

分析

  • 因为是一棵树,如果可以删的话肯定删一条边就够了。
  • 假设1为根,先求红色点的lca,再求蓝色点的lca,如果两个lca不在同一棵子树,那么删掉其中一个连向父节点的边即可。
  • 如果两个lca有祖先关系,比如红色lca和蓝色lca的lca就是红色lca,那么如果红色点都在红色lca指向蓝色lca的另一条边上,那就可以,否则,说明有红色点在蓝色点的子树下。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
struct Edge{
    int v,next;
}e[N*2];
int d[N],fa[N][20],pw[20];
int cnt,head[N];
void init(){
    pw[0]=1;
    for(int i=1;i<=20;i++){
        pw[i]=pw[i-1]*2;
    }
    cnt=0;
    memset(head,-1,sizeof(head));
}
void add(int u,int v){
    e[cnt]=Edge{v,head[u]};
    head[u]=cnt++;
    e[cnt]=Edge{u,head[v]};
    head[v]=cnt++;
}
void dfs(int u){
    for(int i=1;pw[i]<=d[u];i++){
        fa[u][i]=fa[fa[u][i-1]][i-1];
    }
    for(int i=head[u];i!=-1;i=e[i].next){
        int v=e[i].v;
        if(v==fa[u][0]){
            continue;
        }
        fa[v][0]=u;
        d[v]=d[u]+1;
        dfs(v);
    }
}
int lca(int x,int y){
    if(d[x]<d[y]){
        swap(x,y);
    }
    int tmp=d[x]-d[y];
    for(int i=0;pw[i]<=tmp;i++){
        if(tmp&pw[i]){
            x=fa[x][i];
        }
    }
    if(x==y){
        return x;
    }
    for(int i=19;i>=0;i--){
        if(fa[x][i]!=fa[y][i]){
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    return fa[x][0];
}
int n,q,u,v,ri,bi,x,red[N],blue[N];
int main(){
//    freopen("in.txt","r",stdin);
    scanf("%d",&n);
    init();
    for(int i=1;i<n;i++){
        scanf("%d%d",&u,&v);
        add(u,v);
    }
    dfs(1);
    scanf("%d",&q);
    while(q--){
        scanf("%d%d",&ri,&bi);
        scanf("%d",&red[1]);
        int rlc=red[1];
        for(int i=2;i<=ri;i++){
            scanf("%d",&red[i]);
            rlc=lca(rlc,red[i]);
        }
        scanf("%d",&blue[1]);
        int blc=blue[1];
        for(int i=2;i<=bi;i++){
            scanf("%d",&blue[i]);
            blc=lca(blc,blue[i]);
        }
        int lc=lca(rlc,blc);
        if(rlc!=lc && blc!=lc){
            printf("YES\n");
        }else if(rlc==lc){
            bool flag=false;
            for(int i=1;i<=ri;i++){
                if(lca(red[i],blc)==blc){
                    printf("NO\n");
                    flag=true;
                    break;
                }
            }
            if(!flag){
                printf("YES\n");
            }
        }else if(blc==lc){
            bool flag=false;
            for(int i=1;i<=bi;i++){
                if(lca(blue[i],rlc)==rlc){
                    printf("NO\n");
                    flag=true;
                    break;
                }
            }
            if(!flag){
                printf("YES\n");
            }
        }
    }
    return 0;
}

E Third-Party Software - 2

题意

求最少的线段覆盖整个区间。

分析

贪心经典题。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
struct node{
    int l,r,id;
    bool operator <(const node& rhs)const{
        if(l==rhs.l){
            return r>rhs.r;
        }else{
            return l<rhs.l;
        }
    }
}a[N];
int n,m;
vector<int> res;
int main(){
//    freopen("in.txt","r",stdin);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&a[i].l,&a[i].r);
        a[i].id=i;
    }
    sort(a+1,a+1+n);
    int now=1,i=1,ans=0;
    while(i<=n && now<=m){
        int mx=0;
        int k=0;
        while(i<=n && a[i].l<=now){
            if(a[i].r>mx){
                mx=a[i].r;
                k=i;
            }
            i++;
        }
        if(mx<=now-1){
            ans=-1;
            break;
        }
        res.push_back(a[k].id);
        now=mx+1;
        ans++;
    }
    if(now<=m){
        ans=-1;
    }
    if(ans==-1){
        printf("NO\n");
    }else{
        printf("YES\n");
        printf("%d\n",ans);
        for(int i=0;i<ans;i++){
            printf("%d%c",res[i],i==ans-1?'\n':' ');
        }
    }
    return 0;
}

F Friendly Fire

题意

n个怪兽,有两个属性a和b,如果属性a大于另一个怪兽的属性b,就可以打死该怪兽,现在要选出两个怪兽同时打,可能同时死,要使死的怪兽的属性a总和最大。

分析

  • 参考了entry上一个外国朋友的思路https://codeforces.com/blog/entry/67178
  • 考虑分别对a和对b递增排序,得到两个序列,然后枚举第一个序列的怪兽,然后把第二个序列中当前怪兽能打败的怪兽的a值加入优先队列中,因为这些怪兽肯定能被打死,所以直接取出最大的a值,然后再看取出的怪兽能否打败枚举的怪兽,再考虑是否再更新答案。
  • 如果取出的怪兽是本身,就取下一个,注意空队列的判断,取出的怪兽如果没用到,continue之前也要重新入队

代码

#include <bits/stdc++.h>
using namespace std;
const int N=3e5+50;
int n;
struct node{
    int a,b,id;
}a[N],b[N];
bool cmp1(node a,node b){
    if(a.a==b.a){
        return a.b<b.b;
    }else{
        return a.a<b.a;
    }
}
bool cmp2(node a,node b){
    if(a.b==b.b){
        return a.a<b.a;
    }else{
        return a.b<b.b;
    }
}
priority_queue<pair<int,int> > q;
int main(){
//    freopen("in.txt","r",stdin);
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d",&a[i].a,&a[i].b);
        a[i].id=i;
        b[i]=a[i];
    }
    sort(a+1,a+1+n,cmp1);
    sort(b+1,b+1+n,cmp2);
    int ans=0;
    int le=0,ri=0;
    bool up=false;
    for(int i=1,j=1;i<=n;i++){
        while(j<=n && b[j].b<=a[i].a){
            q.push({b[j].a,j});
            j++;
        }
        if(q.empty()){
            continue;
        }
        int mx=q.top().first;
        int idx=q.top().second;
//        printf("%d %d %d\n",i,a[i].a,a[i].b);
//        printf("%d %d %d %d\n",idx,mx,b[idx].id,a[i].id);
        if(b[idx].id==a[i].id){
//            printf("%d F\n",i);
            auto tmp=q.top();
            q.pop();
            if(q.empty()){
                q.push(tmp);
                continue;
            }
            mx=q.top().first;
            idx=q.top().second;
            q.push(tmp);
        }
        int t=mx+(b[idx].a>=a[i].b?a[i].a:0);
        if(t>ans){
            up=true;
            ans=t;
            le=a[i].id;
            ri=b[idx].id;
        }
    }
    printf("%d\n",ans);
    if(!up){
        printf("1 2\n");
    }else{
        printf("%d %d\n",le,ri);
    }
    return 0;
}

H Missing Number

题意

有0-n一共n+1个数,现在删去一个数,通过交互得到删去的数。

交互是询问第x个数的第i位是0还是1。

分析

  • 先分别询问每个数的最低位,统计1的个数,显然如果不缺数的话,1的个数应该是\(\frac{n+1}{2}\),可以由此判断缺的数这一位是0还是1。

  • 然后可以排除掉一半不符合的数,继续询问第2位。
  • 用set存数字下标(即第几个数)比较好写。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+50;
set<int> ns,t;
int main(){
    int n;
    cin >> n;
    for(int i=1;i<=n;i++){
        ns.insert(i);
    }
    //从最低位开始询问
    int i=0;
    int res;
    int ans=0;
    while(ns.size()>=1){
        int sz=ns.size();
        t.clear();
        int b=0;
        for(auto it=ns.begin();it!=ns.end();it++){
            int x=*it;
            //询问第x个数的第i位
            cout << "? " << x << " " << i << endl;
            cout.flush();
            cin >> res;
            if(res){
                t.insert(x);
                b++;
            }
        }
        if(b<(sz+1)/2){
            //缺少的数第i位为1
            ans+=(1<<i);
            ns=t;
        }else{
            for(auto it=t.begin();it!=t.end();it++){
                ns.erase(*it);
            }
        }
        i++;
    }
    cout << "! " << ans << endl;
    return 0;
}

I Painting a Square

题意

一个边长为b的小正方形给一个边长为a的大正方形涂色,问最小需要走的步数。

分析

  • 沿着边缘螺旋走进去是最优的。
  • 所以先沿这边缘走两个边长,就转化为a-b的小正方形的情况了。
  • 本地跑不出来所以手动栈模拟,不过评测机上能跑出来。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a,b;
ll fun(ll a,ll b){
//    printf("%lld %lld\n",a,b);
    if(a-b<b){
        return 3ll*(a-b);
    }else{
        return fun(a-b,b)+2ll*(a-b)+b;
    }
}
stack<ll>st;
ll solve(ll a,ll b){
    ll aa=a;
    while(aa-b>=b)
    {
        st.push(aa);
        aa-=b;
    }
    ll ans=3ll*(aa-b);
    while(!st.empty())
    {
        ans=ans+2ll*(st.top()-b)+b;
        st.pop();
    }
    return ans;
}
int main(){
    scanf("%lld%lld",&a,&b);
//    ll ans=solve(a,b);
    ll ans=fun(a,b);
    printf("%lld\n",ans);
    return 0;
}

J The Power of the Dark Side - 2

题意

n个人,每人有三种属性,如果有两种属性严格大于的话就能击败另一个人,问对于每个人,如果在每场战斗之前都能随意调整属性值,最多能击败多少人。

分析

  • 考虑每个人能被击败的条件,显然就是对方属性值总和至少有(两个最小的属性值+2)。
  • 所以把每个人能被击败的下限求出来,排序,然后枚举每个人的属性总和,upper_bound()查出能击败的人数,再判断这些人里面是否有自身。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+55;
ll a[N][3];
ll sum[N];
int n;
vector<ll> v;
vector<int> ans;
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld%lld%lld",&a[i][0],&a[i][1],&a[i][2]);
        sum[i]=a[i][0]+a[i][1]+a[i][2];
        sort(a[i],a[i]+3);
        v.push_back(a[i][0]+a[i][1]+2);
    }
    sort(v.begin(),v.end());
    for(int i=1;i<=n;i++){
        int k=upper_bound(v.begin(),v.end(),sum[i])-v.begin();
        ll t=sum[i]-(a[i][0]+a[i][1]+2);
        if(t>=0){
            k--;
        }
        ans.push_back(k);
    }
    for(int i=0;i<n;i++){
        printf("%d%c",ans[i],i==n-1?'\n':' ');
    }
    return 0;
}

K Deck Sorting

题意

分析后的题意是给一个只含RGB的字符串,从中抽出一个子序列,和剩下的子序列拼在一起,使得拼接后的字符串相同字符在一起。

分析

  • 枚举6个全排列,也就是拼接后的字符串情况,假设枚举RBG,那么就得把所有R取出。所有G剩下,要考虑的就是B,如果B是在最后一个R的后面,那就是跟随R取出,如果是在第一个G前面,那就跟随G留下,否则,即如果B在最后一个R前面且再第一个G后面,那就是不合法的。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+55;
char s[N];
set<char> lt;
char idx[]={'R','G','B'};
int main(){
    scanf("%s",s+1);
    int n=strlen(s+1);
    for(int i=1;i<=n;i++){
        lt.insert(s[i]);
    }
    int siz=lt.size();
    if(siz<3){
        printf("YES\n");
    }else{
        for(int i=0;i<3;i++){
            char le=idx[i];
            for(int j=0;j<3;j++){
                if(i==j){
                    continue;
                }
                char ri=idx[j];
                char md=idx[3-i-j];
                int leri=-1;
                int rile=-1;
                for(int k=n;k>=1;k--){
                    if(s[k]==le){
                        leri=k;
                        break;
                    }
                }
                for(int k=1;k<=n;k++){
                    if(s[k]==ri){
                        rile=k;
                        break;
                    }
                }
                bool flag=true;
                for(int k=1;k<=n;k++){
                    if(s[k]==md){
                        if(k<leri && k>rile){
                            flag=false;
                            break;
                        }
                    }
                }
                if(flag){
                    printf("YES\n");
                    return 0;
                }
            }
        }
        printf("NO\n");
    }
    return 0;
}

M Shlakoblock is live!

题意

n个东西有p属性和v属性,现在可以选择将一些东西的v属性加1,求最大的每个东西的p的期望。

分析

p越大,v加1对期望的贡献肯定越大,所以对p排序,然后对v加1,更新答案,直到对答案没有影响就退出。

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1050;
int t,n;
struct node{
    ll zi,mu;
};
bool cmp(node a,node b){
    return a.zi*b.mu>a.mu*b.zi;
}
struct info{
    int p,v,id;
    bool operator <(const info& rhs)const{
        return p>rhs.p;
    }
}a[N];
vector<int> ans;
int main(){
    // freopen("in.txt","r",stdin);
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        ll zi=0,mu=0;
        for(int i=1;i<=n;i++){
            scanf("%lld%lld",&a[i].p,&a[i].v);
            zi+=a[i].p*a[i].v;
            mu+=a[i].v;
            a[i].id=i;
        }
        sort(a+1,a+1+n);
        ans.clear();
        node tt{zi,mu};
        for(int i=1;i<=n;i++){
            zi+=a[i].p;
            mu++;
            node tmp={zi,mu};
            if(cmp(tmp,tt)){
                ans.push_back(a[i].id);
                tt=tmp;
            }else{
                break;
            }
        }
        ll g=__gcd(tt.zi,tt.mu);
        printf("%lld/%lld\n",tt.zi/g,tt.mu/g);
        int siz=ans.size();
        sort(ans.begin(),ans.end());
        printf("%d\n",siz);
        if(!siz){
            printf("\n");
        }
        for(int i=0;i<siz;i++){
            printf("%d%c",ans[i],i==siz-1?'\n':' ');
        }
    }
    return 0;
}