2023.8.24
A
保留 \(\{a_n\}\) 中的一些数,使得没有两个数的和为质数。输出集合最终的最大大小。
\(T\le 4\),\(n\le 750\),\(a_i\le 10^9\).
不妨将相同的 \(a_i\) 合并,另外地,\(1\) 至多只能有 \(1\) 个。
这时发现会有若干对数不能共存,考虑建模。
将源点向奇数连边,反之偶数向汇点连边,流量为 \(cnt_i\),有限制的 \(i\) 和 \(j\) 之间由奇数向偶数连一条 inf 的边,跑最小割即可。
使用 Miller_Rabin 可以用 \(O(n^2\sqrt[4]{V})\) 的时间求出限制关系。
实际上只需以 \(2,3,5,7,11\) 为底数就能解决 \(\rm 2e9\) 以内的素性检验。
非常好火车头,长 2.1k.
#include<bits/stdc++.h>
#define N 755
#define M 1200000
#define inf 1e9
#define Test 10
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int qpow(int k,int b,int p){
int ret=1;
while(b){
if(b&1)ret=1ll*ret*k%p;
k=1ll*k*k%p,b>>=1;
}
return ret;
}
bool Miller_Rabin(int n){
if(n==2)return true;
if(n<2||!(n&1))return false;
for(int i=1,a;i<=Test;i++){
a=rand()%(n-2)+2;
if(qpow(a,n-1,n)!=1)return false;
}
return true;
}
int Case,n,a[N],b[N],cnt[N];
int head[N],ver[M],nxt[M],edge[M],tot;
void add(int u,int v,int w){
nxt[++tot]=head[u];
ver[tot]=v,edge[tot]=w;
head[u]=tot;
}
int d[N],now[N],flow,S,T;
bool bfs(){
memset(d,0,sizeof(d));
queue<int>q;
q.push(S),d[S]=1,now[S]=head[S];
while(!q.empty()){
int x=q.front();q.pop();
for(int i=head[x],y;i;i=nxt[i]){
if(edge[i]&&!d[y=ver[i]]){
now[y]=head[y];
q.push(y),d[y]=d[x]+1;
if(y==T)return true;
}
}
}
return false;
}
int dinic(int x,int flow){
if(x==T)return flow;
int ret=0;
for(int i=now[x],y;i;i=nxt[i]){
now[x]=i,y=ver[i];
if(edge[i]&&d[y]==d[x]+1){
int k=dinic(y,min(flow,edge[i]));
if(!k)d[y]=0;
edge[i]-=k,edge[i^1]+=k;
ret+=k,flow-=k;
}
}
return ret;
}
int main(){
srand(233);
Case=read();
while(Case--){
n=read(),memset(head,0,sizeof(head));
memset(cnt,0,sizeof(cnt));
tot=1,S=n+1,T=S+1;
for(int i=1;i<=n;i++)a[i]=read();
sort(a+1,a+1+n);
int st=1;
while(a[st]==1&&a[st+1]==1)st++;
for(int i=1;i<=n;i++)b[i]=a[i];
int len=unique(a+1,a+1+n)-a-1;
for(int i=st;i<=n;i++)
cnt[lower_bound(a+1,a+1+len,b[i])-a]++;
for(int i=1;i<=len;i++){
if(a[i]&1)add(S,i,cnt[i]),add(i,S,0);
else add(i,T,cnt[i]),add(T,i,0);
}
for(int i=1;i<len;i++)
for(int j=i+1;j<=len;j++){
if(Miller_Rabin(a[i]+a[j])){
if(a[i]&1)add(i,j,inf),add(j,i,0);
else add(j,i,inf),add(i,j,0);
}
}
int ans=0;
while(bfs()){
while(flow=dinic(S,inf))
ans+=flow;
}
printf("%d\n",(n-st+1)-ans);
}
return 0;
}
B
\(n\times m\) 的网格上有 \(k\) 个炸弹和 \(b\) 个水晶。
你可以引爆一个炸弹,可以决定它炸行或列。对被连锁的炸弹继续执行操作。
问被炸弹覆盖的水晶的最大个数。
\(n,m\le 3000\),\(k+b\le nm\).
被连锁的炸弹显然应朝被炸的方向的另一边炸。
将每个炸弹朝其上下左右的第一个炸弹连边,形成若干连通块。
现在单独对每个块考虑。
若连通块中有环,那么引爆环中的一个可以把连通块涉及的所有行列引爆。
若没有环,最优解一定为一个左右/上下没有炸弹的炸弹,此时只会损失某行或某列的水晶,减去这个值。
时间复杂度 \(O(nm\cdot\alpha(nm))\).
附上 2.8k 的抽象 std。
#include<bits/stdc++.h>
#define N 3010
#define pb push_back
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int gets(){
char ch=getchar();
while(ch!='k'&&ch!='b'&&ch!='.')ch=getchar();
return ch=='k'?1:(ch=='b'?2:0);
}
int n,m,s[N][N];
int mp[N][N];
int A[N][N],la[N],B[N][N],lb[N];
int f[N*N],tot[N*N],c[N],cnt[N][N];
bool vis[N*N],flag[N*N],vx[N],vy[N];
int id(int x,int y){
return (x-1)*m+y;
}
int find(int x){
return f[x]==x?x:f[x]=find(f[x]);
}
void merge(int x,int y){
x=find(x),y=find(y);
if(x!=y)f[x]=y,flag[y]|=flag[x];
else flag[x]=true;
}
int qry(int l1,int r1,int l2,int r2){
return s[l2][r2]-s[l1-1][r2]-s[l2][r1-1]+s[l1-1][r1-1];
}
int ans;
void solve(int l,int r){
int ret=0;
for(int i=l,x,y;i<=r;i++){
x=(c[i]+m-1)/m,y=c[i]-(x-1)*m;
if(!vx[x]){
vx[x]=true;
for(int j=1;j<=la[x];j++){
if(!cnt[x][A[x][j]])ret++;
cnt[x][A[x][j]]++;
}
}
if(!vy[y]){
vy[y]=true;
for(int j=1;j<=lb[y];j++){
if(!cnt[B[y][j]][y])ret++;
cnt[B[y][j]][y]++;
}
}
}
if(flag[find(c[l])])ans=max(ans,ret);
else{
for(int i=l,x,y,tp;i<=r;i++){
x=(c[i]+m-1)/m,y=c[i]-(x-1)*m;
if(qry(x,1,x,m)==1){
tp=ret;
for(int j=1;j<=la[x];j++)
tp-=(cnt[x][A[x][j]]==1);
ans=max(ans,tp);
}
if(qry(1,y,n,y)==1){
tp=ret;
for(int j=1;j<=lb[y];j++)
tp-=(cnt[B[y][j]][y]==1);
ans=max(ans,tp);
}
}
}
for(int i=l,x,y;i<=r;i++){
x=(c[i]+m-1)/m,y=c[i]-(x-1)*m;
if(vx[x]){
vx[x]=false;
for(int j=1;j<=la[x];j++)cnt[x][A[x][j]]=0;
}
if(vy[y]){
vy[y]=false;
for(int j=1;j<=lb[y];j++)cnt[B[y][j]][y]=0;
}
}
}
int main(){
n=read(),m=read(),read(),read();
for(int i=1;i<=n*m;i++)f[i]=i;
for(int i=1,opt;i<=n;i++)
for(int j=1;j<=m;j++){
mp[i][j]=opt=gets();
if(opt==2)s[i][j]=1;
if(opt==1)
A[i][++la[i]]=j,B[j][++lb[j]]=i;
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
}
for(int i=1;i<=n;i++){
int lst=-1;
for(int j=1;j<=m;j++)
if(mp[i][j]==2){
if(lst!=-1)merge(id(i,lst),id(i,j));
lst=j;
}
}
for(int j=1;j<=m;j++){
int lst=-1;
for(int i=1;i<=n;i++)
if(mp[i][j]==2){
if(lst!=-1)merge(id(lst,j),id(i,j));
lst=i;
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(mp[i][j]==2)tot[find(id(i,j))]++;
for(int i=1;i<=n*m;i++)tot[i]+=tot[i-1];
int sum=tot[n*m];
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(mp[i][j]==2)c[tot[find(id(i,j))]--]=id(i,j);
int lst=0;
for(int i=1;i<=sum+1;i++)
if(i==sum+1||find(c[i])!=find(c[i-1]))
solve(lst+1,i-1),lst=i-1;
printf("%d\n",ans);
return 0;
}
C
\(n\times m\) 的棋盘上有若干棋子,两人移动棋子,规则如下:
-
棋子不能超出棋盘
-
棋子不能走之前走过的点和障碍
-
棋子可以向左、向右、向下走
-
棋子可以从 \((i,1)\) 走到 \((i,m)\),从 \((i,m)\) 走到 \((i,1)\)
无法行动的玩家判负。问谁最终获胜。
注意每个棋子完全独立。
\(n,m\le 1000\),\(\sum n,\sum m\le 4000\).
赛时眼瞎忘了可以往左走丢了 40 分。
考虑将每个棋子初始状态的 SG 函数异或起来。
容易写出 \(O(nm^2)\) 的转移,注意倒序递推。
每行至少有一个障碍
棋子只有三种状态:
-
刚从上一行下来,可以往左右走
-
右边走过了,只能往左走
-
左边走过了,只能往右走
无需记录经过区间,状态数 \(O(nm)\).
无障碍格子
SG 函数可能需要打表找规律。
一般情况
我们需要一种处理无障碍格子的行的方法。
若来到 \((x,y)\),向左走,那么 \((x,y)\) 变为包含障碍的格子,转化为包含障碍格子的情况。
考虑怎么计算 SG 值。
在 \((x,y-1)\) 的格子只能向左或向下走,那么只需求出 \((x,y-2)\) 的 SG 值。
一路递推发现我们要求出 \((x,y+1)\) 的 SG 值,此时只能向下走,那么可以求出该 SG 值并回推。
显然的是 SG 值只能为 \(0,1,2,3\),考虑预处理以下信息:
-
当 \((x,y)\) 的 SG 值为 \(v\) 时,一路向左/右推,位置 \((x,1)\) 或 \((x,m)\) 的 SG 值为多少。
-
当 \((x,1)\) 或 \((x,m)\) 的 SG 值为 \(v\) 时,一路向左/右推,到 \((x,y)\) 时的 SG 值为多少。
这样就可以 \(O(1)\) 计算每个位置的 SG 值。
时间复杂度 \(O(nm)\).
std 达到了优秀的 3k 而且完全看不懂。不知道有没有避免屎山代码的方法。
#include<bits/stdc++.h>
#define N 1005
using namespace std;
int read(){
int x=0,w=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')w=-1;ch=getchar();}
while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*w;
}
int n,m,sg[3][N][N];
int a[4][N],b[4][N],c[4][N],d[4][N];
char mp[N][N];
int SG(int o,int x,int y){
if(sg[o][x][y]!=-1)return sg[o][x][y];
bool v[4];v[0]=v[1]=v[2]=v[3]=false;
if(x<n&&mp[x+1][y]!='#')
v[SG(0,x+1,y)]=true;
int L=(y!=1)?y-1:m,R=(y!=m)?y+1:1;
if(o!=2&&mp[x][L]!='#'&&m!=1)
v[SG(1,x,L)]=true;
if(o!=1&&mp[x][R]!='#'&&m!=1)
v[SG(2,x,R)]=true;
for(int i=0;;i++)
if(!v[i])return sg[o][x][y]=i;
return 10086;
}
void solve(int o){
bool v[4];
for(int u=0;u<4;u++){
int lst=u;
for(int i=1;i<=m;i++){
v[0]=v[1]=v[2]=v[3]=false;
v[lst]=true;
if(o<n&&mp[o+1][i]!='#')
v[SG(0,o+1,i)]=true;
for(int j=0;;j++)
if(!v[j]){lst=j;break;}
a[u][i]=lst;
}
}
for(int u=0;u<4;u++)b[u][m]=u;
for(int i=m-1;i;i--)
for(int u=0;u<4;u++){
v[0]=v[1]=v[2]=v[3]=false;
v[u]=true;
if(o<n&&mp[o+1][i+1]!='#')
v[SG(0,o+1,i+1)]=true;
for(int j=0;;j++)
if(!v[j]){b[u][i]=b[j][i+1];break;}
}
for(int u=0;u<4;u++){
int lst=u;
for(int i=m;i;i--){
v[0]=v[1]=v[2]=v[3]=false;
v[lst]=true;
if(o<n&&mp[o+1][i]!='#')
v[SG(0,o+1,i)]=true;
for(int j=0;;j++)
if(!v[j]){lst=j;break;}
c[u][i]=lst;
}
}
for(int u=0;u<4;u++)d[u][1]=u;
for(int i=2;i<=m;i++)
for(int u=0;u<4;u++){
v[0]=v[1]=v[2]=v[3]=false;
v[u]=true;
if(o<n&&mp[o+1][i-1]!='#')
v[SG(0,o+1,i-1)]=true;
for(int j=0;;j++)
if(!v[j]){d[u][i]=d[j][i-1];break;}
}
for(int i=1;i<=m;i++){
v[0]=v[1]=v[2]=v[3]=false;
if(o<n&&mp[o+1][i]!='#')
v[SG(0,o+1,i)]=true;
if(m!=1){
if(i==1){
int t=(o<n&&mp[o+1][m]!='#')?SG(0,o+1,m):1;
v[c[t][2]]=true;
t=(o<n&&mp[o+1][2]!='#')?(!SG(0,o+1,2)):0;
v[b[t][2]]=true;
}
else if(i==m){
int t=(o<n&&mp[o+1][1]!='#')?SG(0,o+1,1):1;
v[a[t][m-1]]=true;
t=(o<n&&mp[o+1][m-1]!='#')?(!SG(0,o+1,m-1)):0;
v[d[t][m-1]]=true;
}
else{
int t=(o<n&&mp[o+1][i+1]!='#')?(!SG(0,o+1,i+1)):0;
v[a[b[t][i+1]][i-1]]=true;
t=(o<n&&mp[o+1][i-1]!='#')?(!SG(0,o+1,i-1)):0;
v[c[d[t][i-1]][i+1]]=true;
}
}
for(int j=0;;j++)
if(!v[j]){sg[0][o][i]=j;break;}
}
}
int main(){
int T=read();
while(T--){
n=read(),m=read();
for(int k=0;k<3;k++)
for(int i=0;i<=n+1;i++)
for(int j=0;j<=m+1;j++)sg[k][i][j]=-1;
for(int k=0;k<4;k++)
for(int i=0;i<=max(n,m)+1;i++)
a[k][i]=b[k][i]=c[k][i]=d[k][i]=0;
for(int i=1;i<=n;i++)scanf("%s",mp[i]+1);
int ans=0;
for(int i=n;i;i--){
int cnt=0;
for(int j=1;j<=m;j++)cnt+=(mp[i][j]=='#');
if(!cnt)solve(i);
for(int j=1;j<=m;j++)
if(mp[i][j]=='B')ans^=SG(0,i,j);
}
puts(ans?"A":"B");
}
return 0;
}
D
给出一棵树,对于每个点 \(i\),求出
\(n\le 2\times 10^6\),\(0\le d_i\le \sum d_i<2^{31}\).
长剖。我不会。