9.30 CSP-S模拟25 改题记录

HZOJ

写在前面

(其实最讨厌whk和OI转换的时候了,尤其是那种不能完全割舍一方的转换)

嗯对大概就是不想改T4就来写题解了。补课后第一场,人终于都回来了,虽然依旧挂如分,但是竟然能够rk3(?)。感觉这场题还是挺神奇的,值得写一下。

A. 爱丽丝的数位划分

题意大概是给定一个长度为\(n\) 的序列,将其划分成恰好\(k\) 组,每组的贡献恰好是该组所有数的十进制表示出现的种类数,求问最大贡献。
赛时觉得\(O(n^2k)\) 能过挺多的就想着先打个暴力。观察到需要进行或运算并且还要统计个数,考虑使用bitset。对于每一个数求出该数各位出现的种类。枚举\(dp_{i,k}\) 为前\(i\) 个数分成\(k\) 组的最大贡献,枚举每个转移点\(j\)。跑大样例惊人发现超得不多,卡了卡常就卡进去了。然后突然意识到要求恰好分为\(k\) 组,会有许多无用的转移,所以可以确定\(k\) 的上下界,减小枚举范围。除了显然的上界\(min(i,k)\),考虑到每一个点后最多能再分\(n-i\) 组,所以当前点及其之前至少要分\(k-(n-i)\) 组,即下界为\(max(1,k-(n-i))\)。然后测极限数据发现跑得极快。举个例子,原本要枚举\(k=1000\) 现在只用枚\(1\) !!!k的变化趋势应该是个开口向下的单峰函数。然后峰值应该在\(k=500\) 左右。

代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+10;
int dp[maxn][maxn];
int main(){
	freopen("partition.in","r",stdin);
	freopen("partition.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T;
	cin>>T;
	while(T--){
		int n,k;
		cin>>n>>k;
		string a;
		bitset<11> c[maxn];
		for(int i=1;i<=n;i++){
			cin>>a;
			c[i]=0;
			if(a=="0") c[i][0]=1;
			else
				for(int j=0;j<a.size();++j) c[i][a[j]-'0']=1;
		} 
		for(int i=1;i<=n;i++){
			bitset<11> t=0;
			for(int j=1;j<=k;j++) dp[i][j]=0;
			for(int j=i;j>=1;j--){
				t|=c[j];
				for(int kk=max(1,k+i-n);kk<=min(k,j);kk++) dp[i][kk]=max(dp[i][kk],dp[j-1][kk-1]+(int)t.count());
			}
		}
		cout<<dp[n][k]<<'\n';
	}
	return 0;
}

然后看正解。实际上影响每个位置的贡献的只有该位置以前的能组成0-9的位置,所以只用在这些位置转移。复杂度即为\(O(10nk)\)

代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+10;
int dp[maxn][maxn];
int main(){
	freopen("partition.in","r",stdin);
	freopen("partition.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int T;
	cin>>T;
	while(T--){
		int n,k;
		cin>>n>>k;
		string a;
		bitset<11> c[maxn];
		for(int i=1;i<=n;i++){
			cin>>a;
			c[i]=0;
			if(a=="0") c[i][0]=1;
			else
				for(int j=0;j<a.size();++j) c[i][a[j]-'0']=1;
		} 
		for(int i=1;i<=n;i++){
			bitset<11> t=0;
			for(int j=1;j<=k;j++) dp[i][j]=0;
			for(int j=i;j>=1;j--){
				bitset<11> gd=t|c[j];
				if(gd!=t){
					t|=c[j];
					for(int kk=max(1,k+i-n);kk<=min(k,j);kk++) dp[i][kk]=max(dp[i][kk],dp[j-1][kk-1]+(int)t.count());
				}			
				else t|=c[j];
			}
		}
		cout<<dp[n][k]<<'\n';
	}
	return 0;
}

B. 爱丽丝的新运算

简化题意是给定一个每个数质因子次数最多为\(1\) 的序列,求有多少个非空子序列使得该子序列中所有数的所有质因子异或和为\(0\)
一眼转化了题意然后觉得很可做,苦写2小时喜提0pts。原本思路是开个bitset每一位表示数据范围内排行为该位的质数,然后组合计算结果。然后写写错错,错错写写,怀疑那个思路根本不可行,因为以为优化了那么多实际上复杂度还是\(O(2^n)\)。然后喜提0pts。
正解用了没学过的线性基。(异或)线性基类似于向量的一对基底,用集合中的某些元素异或得到集合的所有元素,可用来求某个数是否能由某些数异或得到,或者求某些数的异或最大值。其流程大概是枚举每个元素二进制下的每一位,若该位是1,若基内该位还没有数,则基的该位就是该元素,退出循环;若基内该位有数,则该元素异或上该数再进行循环。这样就可以用部分元素表达所有元素了。
回到本题。那么题意就可转化为有多少个元素能被其他元素表达,令这个数为\(k\),则答案为\(2^k-1\)。回到线性基上就转化成了求有多少个元素在线性基外。\(2^k-1\) 的含义就是代表这些基外元素中选或不选的组合。还有基外元素异或和能被基内元素表达,不会证,但是从答案来看应该是对的。然后显然每个数至多只有1个大于1000的质因数,所以对于质因数都小于1000的数,我们直接尝试将其插入线性基;对于有大于1000的质因数的数,我们单开一个表示该质数的基底。若插入时有该数对应的基底,就异或上该基底然后再往1000内的线性基上插,否则该数就是该质数的基底。由于基底中大于1000的质因数其对应的小于1000的部分可能为0,所有要注意判断是否有基时不能以基为0作为判断条件。然后因为这个糖错狂条inf min。

代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+10,mod=998244353;
int T,n,a[maxn];
int isnp[1001];
int cnt,prime[170];
inline void getprime(){
	for(int i=2;i<=1e3;i++){
		if(!isnp[i]) prime[++cnt]=i,isnp[i]=cnt;
		for(int j=1;j<=cnt&&i*prime[j]<=1e3;j++){
			isnp[i*prime[j]]=1;
			if(i%prime[j]==0) break;
		}
	}
}
inline int qpow(int x,int y){
	int ans=1;
	while(y){
		if(y&1) ans=1ll*ans*x%mod;
		x=1ll*x*x%mod;
		y>>=1;
	}
	return ans;
}
int insta,instia;
bool vis[1000000];
bitset<170> aa[170];
unordered_map<int,bitset<170>> kk;
inline void insert(bitset<170> now,int res){
	if(res>1){
		
		if(!vis[res]){
			++insta;
			kk[res]=now;
			vis[res]=1;
			return;
		}
		now^=kk[res];
	}
	for(int i=168;i>=1;i--)
		if(now[i]){
			if(aa[i].count()==0){
				++insta;
				aa[i]=now;
				return;
			}
			now^=aa[i];
		}
}
int main(){
	freopen("calc.in","r",stdin);
	freopen("calc.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	getprime();
	cin>>T;
	while(T--){
		cin>>n;
		kk.clear();
		memset(aa,0,sizeof(aa));
		memset(vis,0,sizeof(vis));
		insta=0;
		for(int i=1;i<=n;++i) cin>>a[i];
		bitset<170> bb=0;
		for(int i=1;i<=n;i++){
			if(a[i]==1) continue;
			else{
				bb=0; 
				for(int j=1;a[i]>=prime[j]&&j<=cnt;++j)
					if(a[i]%prime[j]==0) a[i]/=prime[j],bb[j]=1;
				insert(bb,a[i]);
			}
		}
			
		cout<<qpow(2,n-insta)-1<<'\n';
	}
	return 0;
}

C. 爱丽丝的幻方

题意是给定一个打乱后的幻方,可以进行下移、右移、沿左上-右下对角线对称、顺时针旋转90度、沿水平轴翻转、沿竖直轴翻转共6种操作,求问最少多少步能还原该幻方。
没思路,时间不够,打了个假的特殊性质,挂了。
惊为天人的探索规律题/模拟题。由于我不会探索规律,所以我选择进行搜索模拟。首先必须注意到一个性质:只要有不在一条直线上的相邻的(即呈“L”形或将其旋转)的三个块的位置关系就能确定幻方的形态。浅证一下:操作1、2不改变形状,操作3对称后三块只有形状、位置改变,相邻关系不变,操作4只转换了方向即改变了形状和位置,不对相邻关系产生影响,操作5、6同理。然后一个幻方由若干个“L”组成,每个“L”间相互支配,故仅需知道一个“L”的形状及其所在位置我们便能确定整个幻方的形态。我们令1为直角顶点,2和n+1为两条边,用该形状和1的位置确定幻方。显然共有8种形态的“L”,1的位置有\(n^2\) 种可能,所以最多会出现\(8n^2\) 种幻方状态。虽然到现在就可以找规律\(O(1)\) 求解了,但我不会。好在状态数在时空允许范围内,转移也能\(O(1)\) 完成,我们就尝试bfs每步的操作直到复原幻方。实现也比较简单,模拟每种状态在某个操作下形状位置的变化情况即可,只不过码量有点大,细节有点多。

代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+10,inf=0x3f3f3f3f;
int T,n,a[maxn][maxn];
inline int getshape(int xa,int ya,int xb,int yb,int xc,int yc){
	if(xa==xb){
		if(ya+1==yb||ya-n+1==yb){
			if(xa+1==xc||xa-n+1==xc) return 1;
			else return 3;
		}
		else{
			if(xa+1==xc||xa-n+1==xc) return 5;
			else return 8;
		}
	}
	else{
		if(ya+1==yc||ya-n+1==yc){
			if(xa+1==xb||xa-n+1==xb) return 2;
			else return 4;
		}
		else{
			if(xa+1==xb||xa-n+1==xb) return 6;
			else return 7;
		}
	}
}
struct node{
	int type,x,y;
	node(int _type,int _x,int _y){
		type=_type,x=_x,y=_y;
	}
};
inline int getsh(node now,int opt){
	int xa,ya,xb,yb,xc,yc;
	xa=now.x,ya=now.y;
	if(now.type==1) xb=xa,yb=(ya+1==n+1?1:ya+1),xc=(xa+1==n+1?1:xa+1),yc=ya;
	else if(now.type==2) xc=xa,yc=(ya+1==n+1?1:ya+1),xb=(xa+1==n+1?1:xa+1),yb=ya;
	else if(now.type==3) xb=xa,yb=(ya+1==n+1?1:ya+1),xc=(xa-1==0?n:xa-1),yc=ya;
	else if(now.type==4) xc=xa,yc=(ya+1==n+1?1:ya+1),xb=(xa-1==0?n:xa-1),yb=ya;
	else if(now.type==5) xb=xa,yb=(ya-1==0?n:ya-1),xc=(xa+1==n+1?1:xa+1),yc=ya;
	else if(now.type==6) xc=xa,yc=(ya-1==0?n:ya-1),xb=(xa+1==n+1?1:xa+1),yb=ya;
	else if(now.type==7) xb=xa,yb=(ya-1==0?n:ya-1),xc=(xa-1==0?n:xa-1),yc=ya;
	else if(now.type==8) xc=xa,yc=(ya-1==0?n:ya-1),xb=(xa-1==0?n:xa-1),yb=ya;
	if(opt==3) return getshape(ya,xa,yb,xb,yc,xc);
	return getshape(xa,ya,xb,yb,xc,yc);
}
queue<node> que;
int vis[9][1001][1001];
inline node opt1(node now){
	now.y++;
	if(now.y==n+1) now.y=1;
	if(vis[now.type][now.x][now.y]!=inf) now.type=-1;
	return now; 
}
inline node opt2(node now){
	now.x++;
	if(now.x==n+1) now.x=1;
	if(vis[now.type][now.x][now.y]!=inf) now.type=-1;
	return now;
}
inline node opt3(node now){
	now.type=getsh(now,3);
	swap(now.x,now.y);
	if(vis[now.type][now.x][now.y]!=inf) now.type=-1;
	return now;
}
inline node opt4(node now){
	if(now.type==1) now.type=6;
	else if(now.type==2) now.type=5;
	else if(now.type==3) now.type=2;
	else if(now.type==4) now.type=1;
	else if(now.type==5) now.type=7;
	else if(now.type==6) now.type=8;
	else if(now.type==7) now.type=3;
	else if(now.type==8) now.type=4;
	swap(now.x,now.y);
	now.y=n-now.y+1;
	if(vis[now.type][now.x][now.y]!=inf) now.type=-1;
	return now;
}
inline node opt6(node now){
	if(now.type==1) now.type=3;
	else if(now.type==2) now.type=4;
	else if(now.type==3) now.type=1;
	else if(now.type==4) now.type=2;
	else if(now.type==5) now.type=8;
	else if(now.type==6) now.type=7;
	else if(now.type==7) now.type=6;
	else if(now.type==8) now.type=5;
	now.x=n-now.x+1;
	if(vis[now.type][now.x][now.y]!=inf) now.type=-1;
	return now;
}
inline node opt5(node now){
	if(now.type==1) now.type=5;
	else if(now.type==2) now.type=6;
	else if(now.type==3) now.type=8;
	else if(now.type==4) now.type=7;
	else if(now.type==5) now.type=1;
	else if(now.type==6) now.type=2;
	else if(now.type==7) now.type=4;
	else if(now.type==8) now.type=3;
	now.y=n-now.y+1;
	if(vis[now.type][now.x][now.y]!=inf) now.type=-1;
	return now;
}
int main(){
	freopen("magic.in","r",stdin);
	freopen("magic.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>T;
	while(T--){
		while(que.size()) que.pop();
		memset(vis,0x3f,sizeof(vis));
		cin>>n;
		int xa,ya,xb,yb,xc,yc;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++){
				cin>>a[i][j];
				if(a[i][j]==1) xa=i,ya=j;
				else if(a[i][j]==2) xb=i,yb=j;
				else if(a[i][j]==n+1) xc=i,yc=j;
			} 
		if(n==1){
			cout<<0<<'\n';
			continue;
		}
		if(n==2){
			if(xa==1&&ya==1){
				if(xb==1&&yb==2) cout<<0<<'\n';
				else cout<<1<<'\n';
			}
			else if(xa==1&&ya==2){
				if(xb==1&&yb==1) cout<<1<<'\n';
				else cout<<2<<'\n';
			}
			else if(xa==2&&ya==1) cout<<1<<'\n';
			else{
				if(xb==1&&yb==2) cout<<1<<'\n';
				else cout<<2<<'\n';
			}
			continue;
		}
		que.push({getshape(xa,ya,xb,yb,xc,yc),xa,ya});
		vis[getshape(xa,ya,xb,yb,xc,yc)][xa][ya]=0;
		if(vis[1][1][1]!=inf){
			cout<<vis[1][1][1]<<'\n';
			continue;
		}
		while(que.size()){
			node a=que.front();
			int k=vis[a.type][a.x][a.y];
			que.pop();
			node b=opt1(a);
			if(b.type!=-1) vis[b.type][b.x][b.y]=min(vis[b.type][b.x][b.y],k+1),que.push(b);
			b=opt2(a);
			if(b.type!=-1) vis[b.type][b.x][b.y]=min(vis[b.type][b.x][b.y],k+1),que.push(b);
			b=opt3(a);
			if(b.type!=-1) vis[b.type][b.x][b.y]=min(vis[b.type][b.x][b.y],k+1),que.push(b);
			b=opt4(a);
			if(b.type!=-1) vis[b.type][b.x][b.y]=min(vis[b.type][b.x][b.y],k+1),que.push(b);
			b=opt5(a);
			if(b.type!=-1) vis[b.type][b.x][b.y]=min(vis[b.type][b.x][b.y],k+1),que.push(b);
			b=opt6(a);
			if(b.type!=-1) vis[b.type][b.x][b.y]=min(vis[b.type][b.x][b.y],k+1),que.push(b);
			if(vis[1][1][1]!=inf) break;
		}
		cout<<vis[1][1][1]<<'\n';
	}
	return 0;
}

D. 爱丽丝斗恶龙

咕了。

posted @ 2025-10-01 19:26  _dlwlrma  阅读(29)  评论(4)    收藏  举报