模拟赛20200302【斯坦纳树+随机映射,二叉树限制步数路径方案数】

T1:LYK loves string

求在字符集大小为 k 的情况下,有多少种长度为 n 的字符串,且该字符串共有 m 个不相同的子串。mod 1e9+7
1<=n<=10,1<=m<=100,1<=k<=1000000000。

题解:

暴力枚举每一位是新加字符还是前面的字符即可。

Code:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int mod = 1e9+7, M = 262143;
int n,m,k,ans,a[15],cnt,fk[15];
vector<LL>vis[M+5];
inline bool ins(LL x){
	int i=x&M,flg=1;
	for(int j=0;j<vis[i].size();j++) if(vis[i][j]==x) {flg=0;break;}
	vis[i].push_back(x);
	return flg;
}
inline void del(LL x){
	int i=x&M;
	for(int j=vis[i].size()-1;j>=0;j--) if(vis[i][j]==x) {vis[i].erase(vis[i].begin()+j);return;}
}
void dfs(int i,int s){
	if(s>m||s+(i+n)*(n-i+1)/2<m) return;
	if(i>n) {ans=(ans+fk[cnt])%mod;return;}
	if(cnt<k){
		a[i]=++cnt;
		LL x=0; int ns=s;
		for(int j=i;j>=1;j--) x=x*(n+1)+a[j],ns+=ins(x);
		dfs(i+1,ns);
		x=0;
		for(int j=i;j>=1;j--) x=x*(n+1)+a[j],del(x);
		cnt--;
	}
	for(int t=1;t<=cnt;t++){
		a[i]=t;
		LL x=0; int ns=s;
		for(int j=i;j>=1;j--) x=x*(n+1)+a[j],ns+=ins(x);
		dfs(i+1,ns);
		x=0;
		for(int j=i;j>=1;j--) x=x*(n+1)+a[j],del(x);
	}
}
int main()
{
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	scanf("%d%d%d",&n,&m,&k);
	fk[0]=1;
	for(int i=1;i<=n;i++) fk[i]=1ll*fk[i-1]*(k-i+1)%mod;
	dfs(1,0);
	printf("%d\n",ans);
}

T2:LYK loves graph

LYK 有一个 n*m 的网格图,每个格子都填有-1 至 n*m-1 中的其中一个数表示它的颜色且每个格子都有一个代价 ai,j。它想选择一个四联通块,使得该四联通块中,存在至少 k 种不同的颜色,且不包含-1,要使得所选的格子的代价和最小。
对于 20%的数据:1<=n,m,k<=4。
对于另外 30%的数据:不同的颜色数<=10(不包括-1)。
对于 100%的数据:1<=n,m<=15,1<=k<=7,1<=ai,j<=100000。

题解:

前20%暴力枚举每个格子选不选。
另外30%就是斯坦纳树的板子,设 f [ i ] [ s ] f[i][s] f[i][s]表示 i i i号点连通了状态为 s s s的颜色的最小代价,初始 f [ i ] [ 1 < < c o l [ i ] ] = a [ i ] f[i][1<<col[i]]=a[i] f[i][1<<col[i]]=a[i],转移有两种:

  • 子集合并: f [ i ] [ s ] = min ⁡ t ⊂ s , t ≠ ∅ f [ i ] [ t ] + f [ i ] [ s   x o r   t ] − a [ i ] f[i][s]=\min_{t\subset s,t\neq\empty}f[i][t]+f[i][s~xor~t]-a[i] f[i][s]=mints,t=f[i][t]+f[i][s xor t]a[i]
  • 新加一个点: f [ i ] [ s ] = min ⁡ i , j 相 邻 f [ j ] [ s ] + a [ i ] f[i][s]=\min_{i,j相邻}f[j][s]+a[i] f[i][s]=mini,jf[j][s]+a[i]

对于第一种转移可以 O ( ( n m ) 2 ∗ 3 k ) O((nm)^2*3^k) O((nm)23k) DP,第二种转移就用SPFA,复杂度 O ( S P F A ( n m , n m ) ∗ 2 k ) O(SPFA(nm,nm)*2^k) O(SPFA(nm,nm)2k)

最后50%的点因为颜色数太多没法存,我们随机将每种颜色映射到 [ 1 , k ] [1,k] [1,k]中,那么随机一次正确的概率就是答案的那几种颜色的映射互不相同的概率,即 6 ! 7 6 ≈ 0.0061 \frac {6!}{7^6}\approx0.0061 766!0.0061,随机300次正确率就达到85%了。

Code:

#include<bits/stdc++.h>
#define maxn 16
using namespace std;
const int inf = 1e9;
int n,m,k,w[maxn][maxn],A[maxn*maxn],c[maxn][maxn],t[maxn*maxn],f[maxn][maxn][1<<7],ans=inf;
struct node{int x,y;};
queue<node>q;
bool inq[maxn][maxn];
int dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
int main()
{
	freopen("graph.in","r",stdin);
	freopen("graph.out","w",stdout);
	srand(55555);
	scanf("%d%d%d",&n,&m,&k);
	for(int i=0;i<n;i++) for(int j=0;j<m;j++) scanf("%d",&c[i][j]);
	for(int i=0;i<n;i++) for(int j=0;j<m;j++) scanf("%d",&w[i][j]);
	for(int i=0;i<n*m;i++) A[i]=i;
	int T=300;
	while(T--){
		random_shuffle(A,A+n*m);//can be deleted.
		for(int i=0;i<k;i++) t[A[i]]=i;//c[i][j] in data is far smaller than 225.
		for(int i=k;i<n*m;i++) t[A[i]]=rand()%k;
		for(int i=0;i<n;i++) for(int j=0;j<m;j++) for(int s=0;s<1<<k;s++) f[i][j][s]=inf;
		for(int i=0;i<n;i++) for(int j=0;j<m;j++) if(~c[i][j]) f[i][j][1<<t[c[i][j]]]=w[i][j];
		for(int s=1;s<1<<k;s++){
			for(int i=0;i<n;i++)
				for(int j=0;j<m;j++) if(~c[i][j]){
					for(int t=(s-1)&s;t;t=(t-1)&s)
						f[i][j][s]=min(f[i][j][s],f[i][j][t]+f[i][j][s^t]-w[i][j]);
					if(f[i][j][s]<ans) q.push((node){i,j}),inq[i][j]=1;
				}
			while(!q.empty()){
				int u=q.front().x,v=q.front().y,x,y; q.pop(),inq[u][v]=0;
				for(int i=0;i<4;i++)
					if((x=u+dx[i])>=0&&x<n&&(y=v+dy[i])>=0&&y<m&&~c[x][y]&&f[x][y][s]>f[u][v][s]+w[x][y])
						if((f[x][y][s]=f[u][v][s]+w[x][y])<ans&&!inq[x][y]) inq[x][y]=1,q.push((node){x,y});
			}
		}
		for(int i=0;i<n;i++) for(int j=0;j<m;j++) ans=min(ans,f[i][j][(1<<k)-1]);
	}
	printf("%d\n",ans);
}

T3:LYK loves rabbits

在这里插入图片描述
k<=100,|a|,|b|,|c|,|x|,|y|,|z|<=10^18。

题解:

参见zbtrs的题解
状态 ( a , b , c ) (a,b,c) (a,b,c)可以从中间往两边跳,也可以由两边的某一边跳进去。往两边跳可以看做往儿子走,往中间跳可以看做往父亲走,那么这就形成了一棵有根无限的二叉树,答案即为状态 S S S到状态 T T T的长度为 k k k的路径数。
因为 k k k很小,所以可以枚举 S S S往上走 k k k步, T T T往上走 k k k步,相同的状态即为它们的 L C A LCA LCA,二叉树上的路径只与两个点到LCA的距离有关,于是就可以DP了。

Code:

#include<bits/stdc++.h>
#define maxn 105
#define LL long long
using namespace std;
const int mod = 1e9+7;
int k,k1,k2,f[maxn][maxn][maxn];
LL a[maxn],b[maxn],c[maxn],x[maxn],y[maxn],z[maxn];
int Up(LL *a,LL *b,LL *c){
	for(int i=0;i<k;i++){
		if(a[i]+c[i]==2*b[i]) return i;
		if(b[i]-a[i]<c[i]-b[i]) a[i+1]=b[i],b[i+1]=2*b[i]-a[i],c[i+1]=c[i];
		else a[i+1]=a[i],b[i+1]=2*b[i]-c[i],c[i+1]=b[i];
	}
	return k;
}
int dfs(int i,int j,int k){
	if(i+j>k) return 0;
	if(!k) return 1;
	if(!i&&j>k2) return 0;
	if(~f[i][j][k]) return f[i][j][k];
	if(i) f[i][j][k]=(dfs(i-1,j,k-1)+2*dfs(i+1,j,k-1)%mod)%mod;
	else{
		if(j) f[i][j][k]=((dfs(i,j+1,k-1)+dfs(i+1,j,k-1))%mod+dfs(i,j-1,k-1))%mod;
		else f[i][j][k]=(dfs(i,j+1,k-1)+2*dfs(i+1,j,k-1)%mod)%mod;
	}
	return f[i][j][k];
}
int main()
{
	freopen("rabbits.in","r",stdin);
	freopen("rabbits.out","w",stdout);
	memset(f,-1,sizeof f);
	scanf("%lld%lld%lld%lld%lld%lld%d",&a[0],&b[0],&c[0],&x[0],&y[0],&z[0],&k);
	k1=Up(a,b,c),k2=Up(x,y,z);
	int d1=-1,d2=-1;
	for(int i=0;i<=k1;i++) for(int j=0;j<=k2;j++)
		if(a[i]==x[j]&&b[i]==y[j]&&c[i]==z[j]) {d1=i,d2=j;goto haha;}
haha:
	if(d1==-1) puts("0");
	else printf("%d\n",dfs(d1,d2,k));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值