暑假D20

NIM游戏改

有m堆石子,A和B轮流取,A先取,每次可以选择一堆石子从中选取任意多石子,最后没有石子取的人输。现每堆非空石子有一次特殊机会,可以耗掉这次机会,什么都不拿。当本来有一次额外机会的石子拿空,机会就没了。

对于前 20%的数据 T=3, n=3 ,  n=3 , A[i]<=4。
对于前 40%的数据T<=100000 , n= 3 , A[i]<=40
对于 100%的数据n<=100000 n*T<= 1000000 , 1<=A[i]<= 1000000000

题解

nim=打表

首先想出建图方式,可以想到拆点——没用特殊机会,用了特殊机会。

那么用了特殊机会的只能向用了的连,没用的就连向没用的和在此时用特殊机会的点连。注意:当为空时不能用特殊机会了,所以没用特殊机会的0不能连向用了特殊机会的0.

如:

然后就可以快乐地打表了!!!

最后发现从0开始的SG值为0,2,1,4,3,6,5....,除0之外是偶数的话SG(x)=x-1,是奇数的话SG(x)=x+1;或者说是两个数交换位置

试着感性理解:对于每个用了一次机会的点,他连向了所有比他小的用了一次机会的,那么他的SG值就是它本身;

那么对于没使用机会的点,拿1,2举例,1除了连向所有比他小的没使用机会的点之外还有他自己使用了一次机会,自己使用机会的SG=1,所以他只有把2给占了;

那么对于2,自己用了一次机会的SG=2,而1的SG=2,他就可以取1的位置,此时0-2的数被取完了,这就表示每再取两个数到编号为i,0-i的数一定都取过。

一开始搞忘可以取任意个....

最后就将每堆的SG值异或起来,=0就是后手胜。

#include<bits/stdc++.h>
using namespace std;

const int maxn=100005;
int t,n,ans;

template<class T>inline void read(T &x){
    x=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
}

/*int mex[maxn];
vector<int>e[maxn];
void dfs(int u){
    if(!u){mex[u]=0;return ;}
    bool b[105];
    memset(b,false,sizeof(b));
    for(unsigned int i=0;i<e[u].size();i++){
        dfs(e[u][i]);
        b[mex[e[u][i]]]=true;
    }
    for(int i=0;i<=100;i++) if(!b[i]) {
        mex[u]=i;return;
    }
}*/

int main(){
    freopen("nim.in","r",stdin);
    freopen("nim.out","w",stdout);
    read(t);
    while(t--){
        read(n);ans=0;
        for(int i=1;i<=n;i++){
            int x;
            read(x);
            if(!x) x=0;
            else if(x&1) x++;
            else x--;
            ans^=x;
        }
        printf("%c\n",!ans ? 'B' : 'A');
    }
}
View Code

Party

有n个人,m个认识关系,认识就是互相认识,先要邀请一些人使得每个人间接或直接认识,并且每个人直接认识至少d个被邀请的人。求最多的人数及邀请的人,如果有多个答案,编号越小越好。

对于100%的数据,2<=n<=2e5,1<=m<=2e5,1<-d<n

题解

对于认识的人不足d的删去,那么最后所有的人认识的人都>=d,那么我们只要找最大的连通块就好。

答案最后一定是整个连通块,首先不可能在两个连通块因为要求直接或间接认识;其次不可能只取一部分,因为都满足条件加进来肯定更优,删去还可能导致其他的点不满足条件。

正确性感性理解:如果答案里面有一个被删去的点,那么他一定不满足度数>=d,那么就需要将一些它连接的删去的点加进来,最后一定会加到一开始就不满足度数>=d的点,那就无法再填这个bug.(随便理解,讲不清楚)

那么删点咋操作?我一开始这样写的

为啥会错?因为当通过x点找到一个需要删去的点y时,删去y也可能带来一些删去的点,而可能y在之前已经跳过了。(真的蠢,感觉还想到过)

所以就需要类似拓扑排序的操作,找到一个需要删的点,如果没打上标记就加入队列,之后删去他的连边。

#include<bits/stdc++.h>
using namespace std;

const int maxn=200005;
int n,m,d;
int du[maxn],ans[maxn],path[maxn];
bool vis[maxn],no[maxn];
vector<int>e[maxn];

template<class T>inline void read(T &x){
    x=0;char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
}

void dfs(int u){
    vis[u]=true;
    path[++path[0]]=u;
    for(unsigned int i=0;i<e[u].size();i++){
        int v=e[u][i];
        if(!vis[v]&&!no[v])
      dfs(v);
    }
}

int main(){
    freopen("party.in","r",stdin);
    freopen("party.out","w",stdout);
    read(n);read(m);read(d);
    for(int i=1;i<=m;i++){
        int x,y;
        read(x);read(y);
        du[x]++;du[y]++;
        e[x].push_back(y);
        e[y].push_back(x);
    }
    queue<int>q ;
    for(int i=1;i<=n;i++)
     if(du[i]<d){
         q.push(i);
         no[i]=true;
     } 
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(unsigned int i=0;i<e[x].size();i++){
            du[e[x][i]]--;
            if(du[e[x][i]]<d&&!no[e[x][i]]){
                no[e[x][i]]=true;
                q.push(e[x][i]);
            }
        }
    }
    for(int i=1;i<=n;i++)
     if(!no[i]&&!vis[i]){
         path[0]=0;
         dfs(i);
         if(ans[0]<path[0]){
             for(int j=0;j<=path[0];j++)
              ans[j]=path[j];
        }
     }
    printf("%d\n",ans[0]);
    sort(ans+1,ans+1+ans[0]);
    for(int i=1;i<=ans[0];i++)
     printf("%d ",ans[i]);
}
/*5 8 3
1 2 1 5
2 3 2 4 2 5
3 4 3 5
4 5*/
View Code

History

有一个奇怪的王国,史书开始记录前有n个城市(编号从0开始),但没有道路。每一年在位的国王都会修一条路;在这之间,国王会进行若干次旅行,对于一次从st到ed的旅行,如果当时能完成而t年前不能,那么这个国王就很高兴,不然就会生气并且在在下一次旅行前让史官记录错误的修建道路信息,即把x,y记成(x+n-c)%n,(y+n-c)%n;

这些年也发生若干次国王的交替,初始国王c=0,每个国王的c值不一定相同,但在位期间c不会改变,新上位时国王是高兴的。

只有修路时年份才+1.

对于每次旅行输出国王是否高兴。(t年前可能是史书记载之前)

 对于100%的数据,n,m<=3e5,0<=c,x,y,st,ed<n,0<=t<m

题解

对于是否能从st到ed用并查集维护即可。如果生气那么真实信息就是(x+c)%n。

考虑如何得到t年前是否有道路,我们可以离线询问,记录需要询问的之前的年份,然后从头跑一边询问并模拟年份,如果这个年份有询问就去查找答案并记录;对于换国王就改c值,记录高兴;修路就改到正确的信息,然和合并并查集即可;最后的旅行意外在之前已经记录了t年前的信息,就只需要再得到当前的信息,当且仅当以前不行现在行时记录高兴并输出答案,不然记录生气并输出答案。

记录的询问要按照年份排序!!!!

还有记录时可能是史前,所以需要对0取max。(没看懂为什么直接搞成把这个询问记录成false就不对)

#include<bits/stdc++.h>
using namespace std;

const int maxn=300005;
int n,m,cnt,year,c;
bool opt=false;//是否生气 
int fa[maxn];
bool ans[maxn];
struct process{
    int x,y,t;
    char op[2];
}a[maxn];
struct question{
    int year,x,y,id;
}q[maxn];

template<class T>inline void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x= f ? -x : x ;
}

bool cmp(question a,question b){
    return a.year<b.year;
}

int find(int x){
    return fa[x]==x ? x : fa[x]=find(fa[x]);
}

int main(){
    freopen("history.in","r",stdin);
    freopen("history.out","w",stdout);
    read(n);read(m);
    for(int i=0;i<n;i++) fa[i]=i;
    for(int i=1;i<=m;i++){
        scanf("%s",a[i].op);
        if(a[i].op[0]=='K') read(a[i].x);
        else if(a[i].op[0]=='R') {read(a[i].x);read(a[i].y);year++;}
        else {
          read(a[i].x);read(a[i].y);read(a[i].t);
          q[++cnt]=(question){max(0,year-a[i].t),a[i].x,a[i].y,i};
        }
    }
    year=0;
    sort(q+1,q+cnt+1,cmp);
    for(int i=1,j=1;i<=m;i++){
        while(j<=cnt&&q[j].year==year){
            int dx=find(q[j].x),dy=find(q[j].y);
            ans[q[j].id]=(dx==dy);
            j++;
        }
        if(a[i].op[0]=='K') {c=a[i].x;opt=false;}
        else if(a[i].op[0]=='R'){
            int x=(a[i].x+c*opt)%n,y=(a[i].y+c*opt)%n;
            int dx=find(x),dy=find(y);
            if(dx!=dy) fa[dx]=dy;
            year++;
        }
        else {
            int dx=find(a[i].x),dy=find(a[i].y);
            if(!ans[i]&&dx==dy) {printf("Y\n");opt=false;}
            else {printf("N\n");opt=true;}
        }
    }
}
View Code

猜你喜欢

转载自www.cnblogs.com/sto324/p/11401610.html