cf训练赛20190726

A. The Door Problem

题目链接
题意:有n扇门,每扇门都由两个开关控制,问能否通过变换使们全部打开。
思路:2-sat。如果门的初始状态为1,则控制它的两个开关,要么同为0,要么同为1。如果门的状态初始为0,则控制它的两个开关一个为0,另一个就为1。需要注意的就是如何建图。

#include<bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f;
typedef long long LL;
const int maxn=4e5+5;
struct Edge
{
    int to,next;
} edge[maxn];
int head[maxn],tot,cnt,sat[maxn<<1],m,n;
bool mark[maxn<<1];
int a[maxn];
vector<int>v[maxn];
void init()
{
    memset(head,-1,sizeof(head));
    tot=cnt=0;
}
 
void add(int u,int valu,int v,int valv)///建图,为了方便通过异或操作来实现集合两个元素的转换
{
    int uu=u*2+valu;
    int vv=v*2+valv;
    edge[tot]= {vv,head[uu]};
    head[uu]=tot++;
    edge[tot]= {uu,head[vv]};
    head[vv]=tot++;
}
///2sat模板
bool dfs(int u)
{
    if(mark[u^1])return 0;
    if(mark[u])return 1;
    mark[u]=1;
    sat[cnt++]=u;
    for(int i=head[u]; i!=-1; i=edge[i].next)
    {
        //cout<<i<<endl;
        int v=edge[i].to;
        if(!dfs(v))
        {
 
            return 0;
        }
    }
    return 1;
}
 
bool twosat()
{
    memset(mark,0,sizeof(mark));
    for(int i=0; i<2*m; i+=2)
    {
        //cout<<n*2<<"**"<<mark[i]<<endl;
        if(mark[i]||mark[i^1])continue;
 
        cnt=0;
        if(!dfs(i))
        {
            while(cnt)mark[sat[--cnt]]=0;
            if(!dfs(i^1))return 0;
        }
    }
    return 1;
}
int main()
{
    //cout<<"***"<<endl;
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for(int i=1; i<=n; i++)
        {
            scanf("%d",&a[i]);
        }
        for(int i=0; i<m; i++)
        {
            int k;
            scanf("%d",&k);
            for(int j=1; j<=k; j++)
            {
                int temp;
                scanf("%d",&temp);
                v[temp].push_back(i);
            }
        }
        for(int i=1; i<=n; i++)
        {
            //cout<<v[i][0]<<","<<v[i][1]<<endl;
            if(a[i]==0)
            {
                add(v[i][0],0,v[i][1],1);
                add(v[i][0],1,v[i][1],0);
            }
            else
            {
                add(v[i][0],1,v[i][1],1);
                add(v[i][0],0,v[i][1],0);
            }
        }
        if(twosat())
        {
            printf("YES\n");
        }
        else printf("NO\n");
    }
}

B. Okabe and Boxes

题目链接
题意:共有2n个操作,分为两种,第一种是向栈中加入一个数,第二种是移除栈顶数,要求移除的数按从1到n,你可以任意排列栈中的数,求满足题意的最小排列次数。
思路:用vector来模拟,如果栈顶不等于当前要删除的数,就把vector清空,ans++。

#include<bits/stdc++.h>
#define LL long long
#define INF 0x3f3f3f3f

using namespace std;

vector<int>v;

int main()
{
    int n;
    scanf("%d",&n);
    int ans=0;
    int temp=1;
    for(int i=1; i<=2*n; i++)
    {
        char s[10];
        scanf("%s",s);
        if(s[0]=='a')
        {
            int x;
            scanf("%d",&x);
            v.push_back(x);
        }
        else
        {
            if(v.empty());
            else if(v.back()==temp)
            {
                v.pop_back();
            }
            else
            {
                ans++;
                v.clear();
            }
            temp++;
        }
    }
    printf("%d\n",ans);
}

C. Cards Sorting

题目链接
题意:有一摞卡片,从最上面开始抽卡片,如果抽到的是当前最小的卡牌,就讲这张卡牌拿出来,否则就将这张卡牌放到牌堆底,问需要抽多少次。
思路:用vector将数值相同的位置存起来,将数组从小到大排序,用树状数组维护区间被删除的数。从小到大处理即可,唯一需要注意的就是可能有相同值的数。

#include<bits/stdc++.h>
#define LL long long
#define INF 0x3f3f3f3f

using namespace std;
const int maxn=1e5+5;

LL n,a[maxn],sum[maxn];

LL lowbit(LL x)///树状数组模板
{
    return x&(-x);
}

void updata(LL x,LL d)
{
    while(x<maxn)
    {
        sum[x]+=d;
        x+=lowbit(x);
    }
}

LL getsum(LL x)
{
    LL res=0;
    while(x>0)
    {
        res+=sum[x];
        x-=lowbit(x);
    }
    return res;
}///树状数组模板

int main()
{
    while(~scanf("%I64d",&n))
    {
        vector<LL>v[maxn];
        for(LL i=1; i<=n; i++)
        {
            scanf("%I64d",&a[i]);
            v[a[i]].push_back(i);
        }
        LL ans=0;
        LL pre=0;
        sort(a+1,a+1+n);
        for(LL i=1; i<=n;)
        {
            LL pos=lower_bound(v[a[i]].begin(),v[a[i]].end(),pre)-v[a[i]].begin();
            LL sz=v[a[i]].size();
            if(pos==0)///如果当前全部最小的数的位置大于上一个最小的数,直接加上
            {
                ans+=v[a[i]][sz-1]-pre-(getsum(v[a[i]][sz-1])-getsum(pre));
                pre=v[a[i]][sz-1];
            }
            else///如果不是全部位置比上一个数大,则需要将牌抽完,再开始新的一轮
            {
                ans+=n-pre-(getsum(n)-getsum(pre))+v[a[i]][pos-1]-getsum(v[a[i]][pos-1]);
                pre=v[a[i]][pos-1];
            }
            for(LL j=0; j<sz; j++)
                updata(v[a[i]][j],1);
            i+=sz;
        }
        printf("%I64d\n",ans);
    }
}

D. Igor and his way to work

题目链接
题意:从S走到T,不能走"*",问是否能在拐不超过两个弯的情况下走到。
思路:dfs,只要注意写法就好。

#include<bits/stdc++.h>
#define LL long long
#define INF 0x3f3f3f3f

using namespace std;
const int maxn=1005;

int n,m;
int vis[maxn][maxn][5];
char mp[maxn][maxn];
int dx[4]= {1,-1,0,0};
int dy[4]= {0,0,1,-1};

bool dfs(int x,int y,int dir,int t)
{
    if(x<1||y<1||x>n||y>m||t>2)
        return 0;
    if(mp[x][y]=='T')
        return 1;
    if(mp[x][y]=='*')
        return 0;
    if(vis[x][y][dir]<=t)///如果走到过这个位置,并且用了更少的拐弯数,则直接return
        return 0;
    vis[x][y][dir]=t;
    for(int i=0; i<4; i++)
    {
        int xx=x+dx[i];
        int yy=y+dy[i];
        if(dir==-1)///-1代表初始时可以任意选择方向
        {
            if(dfs(xx,yy,i,t))
                return 1;
        }
        else if(i==dir)///方向没有改变
        {
            if(dfs(xx,yy,dir,t))
                return 1;
        }
        else///方向发生改变,拐弯数+1
        {
            if(dfs(xx,yy,i,t+1))
                return 1;
        }
    }
    return 0;
}

int main()
{
    memset(vis,INF,sizeof(vis));
    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
    {
        scanf("%s",mp[i]+1);
    }
    int x,y;
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=m; j++)
        {
            if(mp[i][j]=='S')
            {
                x=i,y=j;
            }
        }
    }
    if(dfs(x,y,-1,0))
    {
        printf("YES\n");
    }
    else
        printf("NO\n");
}

E. Sorting the Coins

题目链接
题意:有一个最开始全都为’O’的序列,每次操作将其中一位变成’X’,然后从左往右开始扫描,如果遇到了X并且它的后面是O,则将他们两个交换,直到一次扫描发现没有可以交换的时候,结束。
思路:其实每一次就相当于将最右边的X移动到末尾。所以,就统计下标最大的右边有O的X前面有几个X即可。

#include<bits/stdc++.h>
#define LL long long
#define INF 0x3f3f3f3f

using namespace std;
const int maxn=3e5+5;

int a[maxn];
int pos[maxn];

int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        scanf("%d",&a[i]);
    }
    printf("1");///最开始没有X,只扫一遍
    int k=n,cnt=0;
    for(int i=1; i<=n; i++)
    {
        pos[a[i]]++;
        if(a[i]==k)
        {
            k--;
            while(pos[k])
            {
                k--;
                cnt++;
            }
            cnt++;
        }
        printf(" %d",i-cnt+1);
    }
}

F. Mahmoud and a Dictionary

题目链接
题意:有n个词,每两个词之间有两种关系,一种是同义词,另一种是反义词,如果A与B是同义词,B与C是同义词,那么A与C也是同义词。如果A与B是反义词,B与C是反义词,那么A与C是同义词。给你m种关系,1 x y 代表x和y是同义词,2 x y代表x和y是反义词,问有多少个关系是矛盾的。有q次询问,问x和y是什么关系。
思路:带权并查集裸题。将每个单词用map映射一下,用0表示x与y同义,1表示x与y反义。如果A->B=1,B->C=1则A->C=0,所以值得更新是A->C=(A->B+B->C)%2。

#include<bits/stdc++.h>
#define LL long long
#define INF 0x3f3f3f3f

using namespace std;
const int maxn=1e5+5;

int fa[maxn],rel[maxn],ans,m,q,n;
map<string,int>mp;

void init()
{
    ans=0;
    for(int i=0; i<maxn; i++)
        fa[i]=i;
    memset(rel,0,sizeof(rel));
}

int fin(int x)
{
    if(x==fa[x])
        return x;
    int fx=fa[x];
    fa[x]=fin(fa[x]);
    rel[x]=(rel[x]+rel[fx])%2;
    return fa[x];
}

bool join(int x,int y,int d)
{
    int fx=fin(x);
    int fy=fin(y);
    if(fx==fy)
    {
        if(d!=(rel[x]-rel[y]+2)%2)
        {
            ans++;
            return 1;
        }
    }
    else
    {
        fa[fx]=fy;
        rel[fx]=(rel[y]+d-rel[x])%2;
    }
    return 0;
}

int main()
{
    while(~scanf("%d%d%d",&n,&m,&q))
    {
        init();
        int cnt=0;
        for(int i=1; i<=n; i++)
        {
            string s;
            cin>>s;
            mp[s]=++cnt;
        }
        for(int i=1; i<=m; i++)
        {
            int d;
            string a,b;
            cin>>d>>a>>b;
            if(join(mp[a],mp[b],d-1))
            {
                printf("NO\n");
            }
            else
                printf("YES\n");
        }
        for(int i=1; i<=q; i++)
        {
            string a,b;
            cin>>a>>b;
            int fx=fin(mp[a]);
            int fy=fin(mp[b]);
            if(fx!=fy)
            {
                printf("3\n");
            }
            else
                printf("%d\n",((rel[mp[a]]-rel[mp[b]]+2)%2)+1);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值