CF639(1344,1345)

CF 1345

A

if(n==1||m==1||(n==2&&m==2))
			printf("YES\n");
		else
			printf("NO\n");

B

二分

int f[30010],cnt;
int find(int x)
{
	int l=0,r=cnt,mid;
	while(r-l>1)
	{
		mid=(l+r)>>1;
		if(f[mid]<=x)
			l=mid;
		else
			r=mid;
	}
	return l;
}
int main()
{
	int i,l,r,mid,T,tot,n,flag;
	for(cnt=1;cnt<=30000;cnt++)
	{
		f[cnt]=f[cnt-1]+3*cnt-1;
		if(f[cnt]>1e9)
			break;
	}
	scanf("%d",&T);
	while(T)
	{
		T--; flag=1; tot=0;
		scanf("%d",&n);
		while(n&&flag)
		{
			flag=find(n);
			if(flag)
			{
				tot++;
				n-=f[flag];
			}
		}
		printf("%d\n",tot);
	}
	return 0;
}

C

题意:给一个长度为n的数组a[1,2,……,n],判断是否存在整数i,j,使得i+a[i%n] = j+a[j%n]。
题解:i + a[i % n] = j + a[j % n] 可以转化为 x + p * n + a[x] = y + q * n + a[y] 即 x + a[x] = y + a[y] (mod n) (其中 0 <= x, y < n),统计(i + a[i]) % n是否重复即可。
**P.S.**注意处理负数!!!!

const int N=2e5+10;
int vist[N],a[N];
int main()
{
	int T,i,n,flag;
	scanf("%d",&T);
	while(T)
	{
		T--;
		scanf("%d",&n);
		for(i=0;i<n;i++)
			vist[i]=0;
		flag=1;
		for(i=1;i<=n;i++)
			scanf("%d",&a[i]);
		for(i=1;i<=n;i++)
		{
			if(vist[((a[i]+i)%n+n)%n])//注意处理负数
				flag=0;
			vist[((a[i]+i)%n+n)%n]=1;//注意处理负数
		}
		if(flag)
			printf("YES\n");
		else
			printf("NO\n");
	}
	return 0;
}

D

题意:dfs
方阵中有一些黑格。放置一些N,S极磁体(一个各自可以放多个)。N极不动,S极会动。某一时刻,会随机激活一对S,N磁体,S向N移动。要求:每行(列)必须有N极;任意时刻,S不可离开黑格(即不可移动到白格);每一个黑格必定是某一个S极可以到达的。
题解:方阵中有一些黑格。如果某一行(列)存在有两端以上连续黑格区域(例如###…##),则不合法。如果某行没有黑格,但是所有的列均有黑格,则不合法。(反之同理)如果方阵合法,统计连通块个数。
证明

  1. 如果某一行(列)存在有两端以上连续黑格区域(例如###…##),则不合法。
    例如:
    . . ####
    . . . . ##
    . #####
    由于每一个黑格都存在某一个S可以到达,那么对于s[1][3],s[3][3]两个位置,存在某时刻该位置有S极。又由于每一列有N极,讨论:假设N极在s[1][3],那么S极位于s[3][3]位置时,有可能会被吸引到空白位置(N极在s[3][3]同理);假设N极在s[2][3],那么S极位于s[1][3]和s[3][3]位置时,都有可能被吸到空白位置。
  2. 如果某行没有黑格,但是所有的列均有黑格,则不合法。
    自行体会:
    。. . . .
    . ####
    . ####
    。. . . .
    . ####
    (在“。”处放一个N极即可)
    . . . .
    . ###
    第一行的N极,不论如何放置,都无法满足题意
  3. 如果方阵合法,统计连通块个数。
    对于一个连通块,将其边界放上N极即可。一个连通块只需一个S极。
const int N=1010;
char s[N][N];
int vist[N][N],dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
int judge1(int n,int m)
{
	int i,j,flag;
	for(i=1;i<=n;i++)
	{
		flag=0;
		for(j=2;j<=m;j++)
		{
			if(s[i][j]=='.'&&s[i][j-1]=='#')
				flag=1;
			if(flag&&s[i][j]=='#')
				return 1;
		}
	}
	for(j=1;j<=m;j++)
	{
		flag=0;
		for(i=2;i<=n;i++)
		{
			if(s[i][j]=='.'&&s[i-1][j]=='#')
				flag=1;
			if(flag&&s[i][j]=='#')
				return 1;
		}
	 } 
	return 0;
}
int judge2(int n,int m)
{
	int i,j,flag,f1=0,f2=0;
	for(i=1;i<=n;i++)
	{
		flag=0;
		for(j=1;j<=m;j++)
			if(s[i][j]=='#')
				flag=1;
		if(!flag)
			f1=1;
	}
	for(j=1;j<=m;j++)
	{
		flag=0;
		for(i=1;i<=n;i++)
			if(s[i][j]=='#')
				flag=1;
		if(!flag)
			f2=1;
	}
	if(f1&&f2)
		return 0;
	if(f1||f2)
		return 1;
	return 0;
}
void dfs(int x,int y,int v)
{
	int i,xx,yy;
	vist[x][y]=v;
	for(i=0;i<4;i++)
	{
		xx=x+dx[i];
		yy=y+dy[i];
		if(s[xx][yy]=='#'&&!vist[xx][yy])
			dfs(xx,yy,v);
	}
}
int main()
{
	int n,m,i,j,cnt=0;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
		scanf("%s",s[i]+1);
	if(judge1(n,m)||judge2(n,m))
	{
		printf("-1");
		return 0; 
	}
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)
			if(s[i][j]=='#'&&!vist[i][j])
			{
				cnt++;
				dfs(i,j,cnt);
			}
	printf("%d",cnt);
	return 0;
}

E

题解:拓扑

  1. 若xi < xj,则由xi向xj建边。判断图是否是DAG,若有环,输出-1。
  2. 如果是DAG呢?
    a)首先,一条链上最多有一个全称量词。
    b) 全称量词的位置,在前面的限定大,因此一条链上的点,应当选序号最小的点标记为全称量词。正反两次拓扑,拓扑过程中,找链上序号最小的点。如果一个点在正反两次拓扑中序号均最小,那么该点为A,否则为E。正反两次拓扑,意在寻找这样的点:数值比它小的数的序号均比它大,数值比它大的数的序号均比它大。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=2e5+10; 
struct EDGE{
	int to,nxt;
}edge[2][N*2];
int t[2][N],in[2][N],mina[2][N],tot[2],que[N];
void addedge(int x,int y,int opt)
{
	edge[opt][++tot[opt]].to=y;
	edge[opt][tot[opt]].nxt=t[opt][x];
	t[opt][x]=tot[opt];
	in[opt][y]++;
}
int tp(int opt,int n)
{
	int i,x,y,p,head=0,tail=0;
	for(i=1;i<=n;i++)
		if(!in[opt][i])
			que[++tail]=i;
	while(head<tail)
	{
		head++; x=que[head]; p=t[opt][x];
		while(p)
		{
			y=edge[opt][p].to;
			mina[opt][y]=min(mina[opt][x],mina[opt][y]);
			in[opt][y]--;
			if(!in[opt][y])
				que[++tail]=y;
			p=edge[opt][p].nxt;
		}
	 } 
	 return head;
}
int main()
{
	int n,m,i,x,y,cnt;
	scanf("%d%d",&n,&m);
	for(i=1;i<=n;i++)
		mina[0][i]=mina[1][i]=i;
	for(i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		addedge(x,y,0);
		addedge(y,x,1);
	}
	cnt=tp(0,n);
	if(cnt!=n) { printf("-1"); return 0; }
	tp(1,n);
	cnt=0;
	for(i=1;i<=n;i++)
		if(mina[0][i]==i&&mina[1][i]==i)
			cnt++;
	printf("%d\n",cnt);
	for(i=1;i<=n;i++)
	{
		if(mina[0][i]==i&&mina[1][i]==i)
			printf("A");
		else
			printf("E");
	}
	return 0;
}

F

题解:二分+二分
令g(x) = f(a[i] , x) - f(a[i], x - 1) = x * (a[i] - x * x) - (x - 1) * (a[i] - (x - 1) * (x - 1)) = a[i] - 3x2 + 3x -1。g’(x) = -6x +3 <0 (x>=1) 。贪心,最初每个都是0,每次选g(x)最大的一个。由于一个一个加太慢,直接二分最小的g(x),判断可以选到多少个(再二分一下每个选多少)。
P.S. 记得K开long long ! ! !

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1e5+10;
const long long inf=9e18;
struct NODE{
	long long v, id;
	bool operator < (const NODE & other)
	const { return v>other.v||(v==other.v&&id>other.id);} 
}node[N];
long long a[N],b[N];
long long n;
long long f(long long x,long long y)
{
	return x-3LL*y*y+3LL*y-1;
}
long long find(long long v,long long x)
{
	long long l,r,mid;
	l=0;
	r=x+1;
	while(r-l>1)
	{
		mid=(l+r)>>1;
		if(f(x,mid)>=v)
			l=mid;
		else
			r=mid;
	}
	return l;
 } 
long long solve(long long x)
{
	long long tot=0;
	int i;
	for(i=1;i<=n;i++)
		tot+=(b[i]=find(x,a[i]));
	return tot;
}
int main()
{
	long long k;//k要开long long ! ! ! 
	int i,cnt=0;
	long long l=inf,r=-inf,mid,tot;
	cin>>n>>k;
	for(i=1;i<=n;i++)
	{
		cin>>a[i];
		l=min(l,f(a[i],a[i]));
		r=max(r,f(a[i],1));
	}
	while(r-l>1)
	{
		mid=(l+r)>>1;
		if(solve(mid)<=k)
			r=mid;
		else
			l=mid;
	}
	tot=solve(r);
	for(i=1;i<=n;i++)
		if(b[i]<a[i])
			node[++cnt].v=f(a[i],b[i]+1),node[cnt].id=i;
	sort(node+1,node+cnt+1);
	for(i=1;i<=cnt;i++)
	{
		if(tot==k)
			break;
		b[node[i].id]++;
		tot++;
	}
	for(i=1;i<=n;i++)
		cout<<b[i]<<" ";
	return 0;
 } 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值