cf训练赛20190726
- A. The Door Problem
- B. Okabe and Boxes
- C. Cards Sorting
- D. Igor and his way to work
- E. Sorting the Coins
- F. Mahmoud and a Dictionary
A. The Door Problem
(补)
题意:n个门,m把钥匙,一个钥匙使用一次操作多个门,数据保证每个门有且只有两个钥匙能操作。同一个门操作一次由开变关或者由关变开。最开始n个门的开关状态已给出,问是否存在一种方法使得所有门都能打开。
解:适定性 问题。
该题两个解法,2-sat或者并查集。 (暂时只记录并查集做法)
三个数组:每个门对应的两把钥匙door[][],门的最初状态status[],每个钥匙的状态和并查集f[] 。
记录门01开关状态,记录门i door[i][1,2]两把钥匙的下标,并查集数组f[i]第i把钥匙用,f[i+m]第i把钥匙不用;
想要达到最终状态——门全开,那么对于每个门:
对于开着的门:其两把钥匙要么全用,f[i]连上f[j],要么全不用,f[i+m]连上f[j+m];
对于关着的门:其两把钥匙要么一用一不用f[i]连上f[j+m],要么一关一开,f[i+m]连上f[j];
最后查询每一把钥匙是否存在既用也不用find(i)==find[i+m] (f[i]和f[i+m]有共同祖先),及无法实现。否则则可以实现。
代码:
int f[500005],status[100005],door[100005][4];
int fd(int x){
if(f[x]==x)
return x;
return f[x]=fd(f[x]);
}
void mg(int x,int y){
int fx=fd(x);
int fy=fd(y);
if(fx!=fy){
f[fx]=fy;
}
}
int main()
{
memset(door,0,sizeof(door));
int n,m,num,x;
cin>>n>>m;
for(int i=1; i<=n; i++)
cin>>status[i];
for(int i=1; i<=m; i++)
{
cin>>num;
while(num--)
{
cin>>x;
door[x][++door[x][0]]=i;
}
f[i]=i,f[i+m]=i+m;
}
for(int i=1; i<=n; i++)
{
if(status[i]==1)
{
mg(f[door[i][1]],f[door[i][2]]);
mg(f[door[i][1]+m],f[door[i][2]+m]);
}
else
{
mg(f[door[i][1]+m],f[door[i][2]]);
mg(f[door[i][1]],f[door[i][2]+m]);
}
}
int flag=1;
for(int i=1;i<=m;i++)
{
if(fd(i)==fd(i+m))
{
flag=0;
cout<<"NO"<<endl;
break;
}
}
if(flag)
{
cout<<"YES"<<endl;
}
}
B. Okabe and Boxes
(补)
题意:两个操作,add:往栈里push一个数。remove。移除栈顶元素。现在要求将数字按1-n顺序移除。问你需要改动多少次后满足。改动:当遇到不满足时,可以将栈内元素排序。
解:
比赛时写的其实跟答案很接近了,就是没想到当不满足时,其实可以直接把栈清空。这样的话,当前这个数是要删的或者栈为空时都是满足。
stack<int>q;
int main()
{
int n;
scanf("%d",&n);
char s[10];
int cnt=0,ans=0,x,mx=0;
n=n*2;
for(int i=1; i<=n; i++)
{
scanf("%s",&s);
if(s[0]=='a')
{
scanf("%d",&x);
q.push(x);
}
else
{
++cnt;
if(q.empty())continue;
else if(q.top()==cnt)
{
q.pop();
}
else
{
ans++;
while(!q.empty())q.pop();
}
}
}
printf("%d\n",ans);
}
C. Cards Sorting
(补)
题意:n个牌,牌上有数,每次从牌顶抽取一张牌,如果这张牌是当前的最小值那么就把它扔掉,不然放到牌堆底,问需要进行多少次抽取牌的操作把牌扔光。
解:
大多数人做法是树状数组,我补题的时候没那么做,在这里先写自己的做法,树状数组做法有空再补。
我的做法跑了140msO(nlogn+),树状数组平均是60ms。
struct node
{
int a,b,c;
} f[100005];
bool cmp(node x,node y)
{
if(x.a==y.a)return x.b<y.b;
return x.a<y.a;
}
int main()
{
int n;
cin>>n;
for(int i=1; i<=n; i++)
{
cin>>f[i].a;
f[i].b=i;
f[i].c=0;
}
sort(f+1,f+1+n,cmp);
long long la=0,tmp=0,cnt=0,ans=0;
//for(int i=1; i<=n; i++)cout<<f[i].a<<" "<<f[i].b<<endl;
//cout<<"!!"<<endl;
for(int i=1; i<=n; i++)
{
if(f[i].c==0){
if(f[i].b>la)
{
f[i].c=1;
cnt++;
}
else
{
for(int j=i+1;j<=n;j++)
{
if(f[j].a==f[i].a)
{
if(f[j].b>la)
{
cnt++;
la=f[j].b;
f[j].c=1;
}
}
else break;
}
// cout<<n-tmp<<endl;
ans+=n-tmp;
f[i].c=1;
tmp=cnt;
cnt++;
}
la=f[i].b;
}
}
ans+=n-tmp;
cout<<ans<<endl;
}
D. Igor and his way to work
(补)
题意:给一张n*m的图,图上有障碍,从S点开始,只能上下左右走,问能否最多只转弯两次到达T点。
解:
直接DFS跑一遍完事。
写的时候第一次按照for循环,能走的点就标记走,走完删除标记再走下一个方向,然后T了几发。看了网上的方法,改进成再加一维数组记录每个点的方向。走到这个点这个方向就记录下来此时转弯了几次,下次再走到这个点这个方向,看看如果转弯次数比记录还大,那就不跑这个点了,直接continue。思路懂了,也能完全自己写出来,但是没明白为什么时间复杂度能降这么多,直接从3000ms降到300ms。
还有一种写法是模拟,因为只能拐弯两次,所以路线必定是一条线或者拐一次或者Z字型,检查每一行的S和T之间的通路,每一列S和T之间的通路,如果有,就可以。
DFS做法模板:
#include<iostream>
#include<cstring>
using namespace std;;
char s[1005][1005];
int xi[5]= {-1,1,0,0};
int yj[5]= {0,0,-1,1};
int mark[1005][1005][5];
int ans=0;
int n,m,ei,ej,si,sj;
const int inf=0x3f3f3f3f;
void dfs(int i,int j,int toward,int cnt)
{
if(i==ei&&j==ej){
ans=1;
return;
}
int x,y;
for(int z=0; z<4&&ans==0; z++)
{
x=i+xi[z];
y=j+yj[z];
if(x>=0&&x<n&&y>=0&&y<m&&s[x][y]!='*'&&ans==0)
{
if(mark[x][y][z]<=cnt)
continue;
else if(z!=toward&&toward!=-1)
{
if(cnt>=2)continue;
cnt++;
mark[x][y][z]=cnt;
dfs(x,y,z,cnt);
cnt--;
}
else
{
mark[x][y][z]=cnt;
dfs(x,y,z,cnt);
}
}
}
}
int main()
{
ans=0;
memset(mark,inf,sizeof(mark));
cin>>n>>m;
for(int i=0; i<n; i++)
cin>>s[i];
for(int i=0; i<n; i++)
{
for(int j=0; j<m; j++)
{
if(s[i][j]=='S')
{
si=i;
sj=j;
}
else if(s[i][j]=='T')
{
ei=i;
ej=j;
}
}
}
dfs(si,sj,-1,0);
if(ans==1)cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}
E. Sorting the Coins
题意:有n个硬币,刚开始所有的硬币均为不流通,然后分开n次使得n个硬币流通,小明每次只能从左到右看,不能返回,且不能用手改变位置,若看的位置是流通硬币且下一个硬币是不流通的,则小明则在心中将这两个硬币交换位置,若前后两个硬币均为流通或不流通则不交换,问从全不流通到一个一个地把每一个变成流通,每一次变后,将所有流通硬币全部挪到右边最少需要挪几次。
解:
只能从左往右挪动,每一个流通每次只能挪到下一个流通的左边一个位置。所以相当于每次只能将最右边还没到最终位置的流通硬币挪到最终位置,所以,有几个还没挪到最终位置的流通硬币,就需要挪几次。
int a[300005],b[300005];
int main()
{
int n,k;
cin>>n;
k=n;
memset(b,0,sizeof(b));
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
cout<<1<<" ";
for(int i=1;i<n;i++)
{
b[a[i]]=1;
for(int j=k;j>=1;j--)
{
if(b[j]==0)
{
k=j;
break;
}
}
if(i!=n)cout<<i-(n-k)+1<<" ";
}
cout<<1<<endl;
}