AGC 014

A
发现当三个数都一样时答案是无穷,否则直接模拟就好了,步数不会太多(可以证明最多log次)

B
模型转化,在mod 2意义下,x到y+1,和 根到x+1根到y+1是等效的,所以每个点出现次数都是偶数则有解,否则某些点的父边会是奇数,无解

C
一开始走完k步后,可以开k步走k步,相当于图中没有障碍了,所以bfs一下一开始k步能到哪些点,然后枚举这些点看他们至少操作多少次到达边界

D
当最终的树上有某个白点不和黑点相连,先手赢,那么度数越小的白点越容易成为答案
先考虑叶子节点,先手先取最深的某个叶子节点的父亲,后手一定要取这个叶子,这时如果这个父亲还有其他叶子孩子则先手直接胜利,否则相当于从原树中去掉了这两个点
感觉根可以随便定,bfs一遍后,按深度从大到小染色,去掉这些成对的点,染完还要额外判一下有没有不与黑点相连的点(有特殊情况)

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int maxn = 210000;

int n,m,d[maxn];
struct edge{int y,nex;}a[maxn<<1]; int len,fir[maxn];
inline void ins(const int x,const int y){a[++len]=(edge){y,fir[x]};fir[x]=len;}

int fa[maxn],dep[maxn];
void dfs(const int x)
{
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(y!=fa[x])
        dep[y]=dep[x]+1,fa[y]=x,dfs(y);
}

int col[maxn];
struct node{int d,x;};
inline bool operator <(const node x,const node y){return x.d<y.d;}
priority_queue<node>q;
bool Col()
{
    for(int i=1;i<=n;i++) q.push((node){dep[i],i});
    memset(col,-1,sizeof col);
    while(!q.empty())
    {
        const node now=q.top(); q.pop();
        int x=now.x;
        if(col[x]!=-1) continue;
        if(x==1) {col[x]=0; break;}
        if(col[fa[x]]!=-1) return true;
        col[fa[x]]=0;
        col[x]=1;
        for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) d[a[k].y]++;
    }
    return false;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int x,y; scanf("%d%d",&x,&y);
        ins(x,y); ins(y,x);
    }
    dep[1]=1; dfs(1);
    if(Col()) return puts("First"),0;
    for(int i=1;i<=n;i++) if(!col[i]&&!d[i]) return puts("First"),0;
    puts("Second");

    return 0;
}

E
考虑一次操作,先删掉一条原树的边,此时树变成了两个联通块,然后添加一条连接这两个联通块的新边,然后对这2个联通块内部进行操作,如果能操作n-1次就合法,最后一次操作肯定是删掉两点间的边再添加回去
我们倒着考虑这个过程,就是每次选一对连接相同点的边,将这两个点合并成一个点,因为对于一棵树,两个联通块之间至多只有一条边相连,所以两棵树至多2条,所以不需要考虑边的颜色
于是维护一个multiset存连接相同点的边对,每个点开multiset存和他相连的点,再开一个map[x][y]存x和y之间的边数,每次合并点时启发式合并,nlog^2n

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int maxn = 210000;

int n,m;
struct edge{int x,y;edge(){}edge(const int _x,const int _y){x=_x;y=_y;}};
inline bool operator <(const edge x,const edge y){return x.x==y.x?x.y<y.y:x.x<y.x;}

map<int,int>mp[maxn];
multiset<edge>q;
multiset<edge>::iterator it;
multiset<int>S[maxn];
multiset<int>::iterator it2;

int num[maxn];

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) num[i]=1;
    for(int i=1;i<=2*(n-1);i++)
    {
        int x,y; scanf("%d%d",&x,&y);
        mp[x][y]++; mp[y][x]++;
        if(mp[x][y]==2) q.insert(x<y?edge(x,y):edge(y,x));
        S[x].insert(y); S[y].insert(x);
    }
    for(int i=1;i<n;i++) 
    {
        it=q.begin(); if(it==q.end()) return puts("NO"),0;

        int x=(*it).x,y=(*it).y; q.erase(it);
        if(num[x]<num[y]) swap(x,y);
        num[x]+=num[y];

        for(it2=S[y].begin();it2!=S[y].end();it2++)
        {
            int k=(*it2); if(k==x) { S[x].erase(S[x].find(y));continue; }

            if(mp[y][k]==2) q.erase(q.find(y<k?edge(y,k):edge(k,y)));
            mp[y][k]--; mp[k][y]--;
            S[k].erase(S[k].find(y));

            if(mp[x].count(k)>0&&mp[x][k]==2) return puts("NO"),0;
            mp[x][k]++; mp[k][x]++;
            S[x].insert(k); S[k].insert(x);
            if(mp[x][k]==2) q.insert(x<k?edge(x,k):edge(k,x));
        }
    }
    puts("YES");

    return 0;
}

F
发现1对2~n的排序没影响,设将2~n的序列排至有序需要T2次,对原序列操作T2次后,若1在开头则T1=T2,否则T1=T2+1
考虑对2~n序列操作T2-1后的序列,设开头是fi,显然fi>2,否则T2-1+1次操作后不能使2~n变得有序,或T2-1次后已经有序
这时,若1在fi和2之间,T1=T2,否则T1=T2+1,这里有个性质,(fi,1,2)的顺序旋转0~2次和最开始的顺序相同

证明:
先证明fi只有在序列开头时才是high element其他情况都是low:若fi不在开头时成为了high,则前面一定有某个y< fi是high,排一次后y和fi挨着,fi要成为开头,一定要和y分离,若分离,只能是y为low,fi为high,这时候前面一定存在某个y’< fi,回到原来的状况,因此fi永远不能成为开头,反证得到若fi是T2-1次的开头,当且仅当fi是开头时fi是high
然后分类讨论fi,1,2在序列中的位置,证明i+1->i时这个性质成立,归纳得(fi,1,2)圆排不变这个性质成立

于是可以通过原序列的位置关系判断是否满足1在fi和2中间 于是可以由Tn递推到T1,维护fi,复杂度O(n)

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int maxn = 210000;

int n;
int a[maxn],p[maxn];
int T[maxn],f[maxn];

bool judge(const int x,const int y,const int z){return x<y?(z<x||z>y):(y<z&&z<x);}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),p[a[i]]=i;

    T[n]=0;
    for(int i=n-1;i>=1;i--)
    {
        if(!T[i+1])
        {
            if(p[i]<p[i+1]) T[i]=0;
            else T[i]=1,f[i]=i+1;
        }
        else
        {
            if(judge(p[f[i+1]],p[i],p[i+1])) T[i]=T[i+1],f[i]=f[i+1];
            else T[i]=T[i+1]+1,f[i]=i+1;
        }
    }
    printf("%d\n",T[1]);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值