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极可以到达的。
题解:方阵中有一些黑格。如果某一行(列)存在有两端以上连续黑格区域(例如###…##),则不合法。如果某行没有黑格,但是所有的列均有黑格,则不合法。(反之同理)如果方阵合法,统计连通块个数。
证明:
- 如果某一行(列)存在有两端以上连续黑格区域(例如###…##),则不合法。
例如:
. . ####
. . . . ##
. #####
由于每一个黑格都存在某一个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]位置时,都有可能被吸到空白位置。 - 如果某行没有黑格,但是所有的列均有黑格,则不合法。
自行体会:
。. . . .
. ####
. ####
。. . . .
. ####
(在“。”处放一个N极即可)
. . . .
. ###第一行的N极,不论如何放置,都无法满足题意
- 如果方阵合法,统计连通块个数。
对于一个连通块,将其边界放上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
题解:拓扑
- 若xi < xj,则由xi向xj建边。判断图是否是DAG,若有环,输出-1。
- 如果是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;
}