郑州集训day1自闭有感

被拉到郑州培训了

考了一上午莫名自闭

帮助慎老师拿到\(rk1\)非常开心

简述一下题目吧

T1.まんふは函数

原题地址

考原题还行

据说是\(Huffman\)

在成爷爷的再三讲解下,我终于明白了

我们可以把\(f(i,j)\)理解为合并了\(n-i+1\)个点,形成了\(j\)棵树这个状态到最终状态也就是\(n\)个点\(1\)棵树的最小代价

于是来思考一下这个方程

\[f(i,j)=min\{f(i-1,j+1),f(i,ceil(j/2)+b_i)\} \]

第一个方程是新增加了一个节点,这个节点独立作为了一棵树,所以没有什么算贡献的必要

第二个方程比较\(nb\)了就是把现在已经形成的\(j\)\(Huffman\)树两两合并,之后算贡献,因为是从\(n\)往前合并的,于是合并一次的话花费是后面\(n-i+1\)个数的和,也就是\(b_i\)

于是问题等价于把这\(n\)个元素合并成一棵树的最小代价

这个的话,不知道合并果子您做过没有

T2.穿越广场

【问题描述】

$L $国的仪仗队要穿越首都广场了。

首都广场可以看做是一块 \(N*M\) 的矩形网 格,仪仗队要从左上角的格点\((0,0)\)行进到右下角的格点\((N,M)\),行进过程中只能 向右走或者向下走。

如果把向右走记为\(R\),把向下走记为\(D\),则仪仗队的 行进序列是一个包含 $M \(个\)R$和 \(N\)\(D\)的字符串。

这时,$L $国的首长又提出了一个奇葩的要求。他认为仪仗队行走的序列中必 须包含他给出的两个字符串。请你计算一下,满足首长要求的行进序列有多少种 呢?

【输入格式】

从文件 \(square.in\) 中读入数据。 第一行一个整数 \(T\),表示数据组数。 每组数据的第一行是两个整数 \(M\)\(N\),表示行进序列由 \(M\)\(R\)\(N\) 个$ D$构成。 每组数据的第二行和第三行是两个不相同的字符串,表示首长要求这两个字 符串是行进序列的子串。

【输出格式】

输出到文件$ square.out$ 中。 一个整数,表示满足要求的行进序列的数量模 $1000000007 $的值。

【样例输入 1】

2

3 2

RRD

DDR

3 2

R

D

【样例输出 1】

1

10

【数据规模与约定】

对于 \(20\%\)的数据,字符串长度\(<=2\)

对于 \(50\%\) 的数据,\(1<=N,M<=50\),字符串长度\(<=50,T=1\)

对于 \(100\%\) 的数据,\(1<=N,M<=100\),字符串由\(R\)\(D\)组成且长度\(<=100\)\(1<=T<=10\)

这个\(t2\)就不是原题了,但是还是非常套路的

先看一下\(50\)分的做法,我们甚至可以来一个\(O(n^4)\)的做法

非常显然我们可以设\(dp[i][j][k][p]\)表示走到了\((i,j)\)这个格子,在第一个串里匹配到了\(k\)位置,在第二个串里匹配到了\(p\)位置的方案数

转移的话我们需要枚举下一步是走\(D\)还是\(R\),之后更新出新的匹配位置,这个需要我们提前处理好第一个串和第二个串的\(next\)数组,之后每次转移就像\(kmp\)那样匹配就好了

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#define re register
#define maxn 80
#define LL long long
#define inf 999999999
#define max(a,b) ((a)>(B)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
const int mod=1e9+7;
inline int read()
{
	char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
char S[2][maxn];
short nx[2][maxn];
short n,m,len[2],T;
int dp[maxn][maxn][maxn][maxn];
inline void getnx(int o)
{
	memset(nx[o],0,sizeof(nx[o]));
	nx[o][1]=0;
	len[o]=strlen(S[o]+1);
	for(re int i=2;i<=len[o];i++)
	{
		int p=nx[o][i-1];
		while(p&&S[o][p+1]!=S[o][i]) p=nx[o][p];
		if(S[o][p+1]==S[o][i]) nx[o][i]=p+1;else nx[o][p]=0;
	}
}
int main()
{
	T=read();
	while(T--)
	{
		m=read(),n=read();
		scanf("%s",S[0]+1),scanf("%s",S[1]+1);
		getnx(0),getnx(1);
		memset(dp,0,sizeof(dp));
		dp[0][0][0][0]=1;
		for(re int i=0;i<=n;i++)
			for(re int j=0;j<=m;j++)
				for(re int k=0;k<=len[0];k++)
					for(re int p=0;p<=len[1];p++)
					{
						if(!dp[i][j][k][p]) continue;
						int kk=0,pp=0;
						if(k==len[0]) kk=k;
						if(p==len[1]) pp=p;
						if(i!=n)
						{
							if(!kk)
							{
								kk=k;
								while(kk&&S[0][kk+1]!='D') kk=nx[0][kk];
								if(S[0][kk+1]=='D') kk++;
									else kk=0;
							}
							if(!pp)
							{
								pp=p;
								while(pp&&S[1][pp+1]!='D') pp=nx[1][pp];
								if(S[1][pp+1]=='D') pp++;
									else pp=0;
							}
							dp[i+1][j][kk][pp]=(dp[i+1][j][kk][pp]+dp[i][j][k][p])%mod;
						}
						kk=0,pp=0;
						if(k==len[0]) kk=k;
						if(p==len[1]) pp=p;
						if(j!=m)
						{
							if(!kk)
							{
								kk=k;
								while(kk&&S[0][kk+1]!='R') kk=nx[0][kk];
								if(S[0][kk+1]=='R') kk++;
									else kk=0;
							}
							if(!pp)
							{
								pp=p;
								while(pp&&S[1][pp+1]!='R') pp=nx[1][pp];
								if(S[1][pp+1]=='R') pp++;
									else pp=0;
							}
							dp[i][j+1][kk][pp]=(dp[i][j+1][kk][pp]+dp[i][j][k][p])%mod;
						}
					}
		printf("%d\n",dp[n][m][len[0]][len[1]]);
	}
	return 0;
}

发现我们记录两个串的匹配位置真是太奢侈了,我们考虑把这两个串的信息整合一下

发现我们只需要开一个\(AC\)自动机就好了呀

于是设\(dp[i][j][k][0/1/2/3]\)表示到格子\((i,j)\)在自动机上走到了\(k\)位置,匹配的状态是\(0/1/2/3\)这些个二进制数

我们发现这个样子就非常好转移了,每次需要转移的话直接利用\(son[k][R]\)\(son[k][D]\)同时维护出结束标记就好了

代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#define re register
#define maxn 105
#define LL long long
#define inf 999999999
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
const int mod=1e9+7;
inline int read()
{
	char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-48,c=getchar();return x;
}
char S[2][maxn];
int n,m,T,len[2],cnt;
int son[maxn+maxn][2],flag[maxn+maxn],fail[maxn+maxn];
int dp[maxn][maxn][maxn+maxn][4];
inline void ins(int o)
{
	len[o]=strlen(S[o]+1);
	int now=0;
	for(re int i=1;i<=len[o];i++) 
		if(S[o][i]=='D') S[o][i]=0;else S[o][i]=1;
	for(re int i=1;i<=len[o];i++)
	{
		if(!son[now][S[o][i]])  son[now][S[o][i]]=++cnt;
		now=son[now][S[o][i]];
	}
	flag[now]|=(1<<(o)); 
} 
inline void Build()
{
	std::queue<int> q;
	for(re int i=0;i<2;i++) if(son[0][i]) q.push(son[0][i]);
	while(!q.empty())
	{
		int k=q.front();q.pop();
		flag[k]|=flag[fail[k]];
		for(re int i=0;i<2;i++)
		if(son[k][i]) fail[son[k][i]]=son[fail[k]][i],q.push(son[k][i]);
			else son[k][i]=son[fail[k]][i];
	}
}
int main()
{
	freopen("square.in","r",stdin);
	freopen("square.out","w",stdout); 
	T=read();
	while(T--)
	{
		m=read(),n=read();
		scanf("%s",S[0]+1),scanf("%s",S[1]+1);
		memset(dp,0,sizeof(dp)),memset(son,0,sizeof(son)),memset(flag,0,sizeof(flag)),memset(fail,0,sizeof(fail));
		cnt=0,ins(0),ins(1),Build();
		dp[0][0][0][0]=1;
		for(re int i=0;i<=n;i++)
			for(re int j=0;j<=m;j++)
				for(re int k=0;k<=cnt;k++)
					for(re int p=0;p<4;p++)
					if(dp[i][j][k][p])
					{
						if(i!=n)
						{
							int kk=son[k][0];
							dp[i+1][j][kk][p|flag[kk]]=(dp[i+1][j][kk][p|flag[kk]]+dp[i][j][k][p])%mod;
						}
						if(j!=m)
						{
							int kk=son[k][1];
							dp[i][j+1][kk][p|flag[kk]]=(dp[i][j+1][kk][p|flag[kk]]+dp[i][j][k][p])%mod;
						}
					} 
		int ans=0;
		for(re int i=0;i<=cnt;i++) ans=(ans+dp[n][m][i][3])%mod;
		printf("%d\n",ans); 
	}
	return 0;
}

T3.存印器

【问题描述】

一个存印器是包含 \(M\) 个变量并且可以接受两种指令的机器,这两种指令分 别为:

  1. \(variable=integer\)

  2. \(print(variable)\)

\(variable\) 可以用 \(M\) 个变量中的任意一个变量的名称替换,变量名称用一个小 写字母表示。\(integer\) 可以用任意整数替换。

$print $打印出变量中当前存储的值。 存印器执行一次变量赋值操作需要耗费的代价为 \(integer\) 转化为二进制数后 包含 $1 $的个数。执行打印操作不耗费代价。

现在有一个长度为$ N$ 的整数序列需要打印。如果用存印器按顺序打印这个序 列,至少需要多少代价呢?

【输入格式】

从文件$ saveprint.in$ 中读入数据。 第一行两个整数 \(N,M\)。 第二行 \(N\) 个整数,表示需要打印的序列。

【输出格式】

输出到文件$ saveprint.out$ 中。 输出一个整数表示最小代价。

【样例输入 1】

7 2

1 2 2 4 2 1 2

【样例输出 1】

4

【数据规模与约定】

对于 \(20\%\)的数据,\(1≤n≤10\)

对于 \(50\%\)的数据,\(1≤m≤2\)

对于 \(100\%\)的数据,\(1≤n≤250\), \(1≤m≤26\),序列中的整数在\(1-10^9\)范围 内。

写了一个\(50\)\(m=2\)\(dp\)拿了\(60\)非常开心

\(dp\)太傻了就不说了

这题正解一看就是网络流啊,而且一看就是费用流

发现这个其实和某一道最小权路径覆盖一模一样啊

我们把每个点\(i\)拆成\(i\)\(i'\)两个点,之后搞一个超级源点\(S\)向每一个\(i\)连一条容量为\(1\)费用为\(0\)的边,\(i'\)\(T\)连容量为\(1\)费用为\(0\)的边

之后每个点\(i\)\((i+1)',(i+2)'...n'\)连容量为\(1\)费用为\(bit\)的边,\(bit\)为指向的点的二进制中\(1\)的个数,如果这条边连接的是两个权值相同的点那么这条边的费用为\(0\),这样就可以表示存印器里权值的切换

之后\(S\)\(S'\)连容量为\(m\)的边,\(S'\)向所有的\(i'\)连边,容量为\(1\),费用为对应的\(bit\),表示存印器刚开始被存入了这个权值

代码就不写了,就是一个费用流的板子了

posted @ 2019-01-26 17:42  asuldb  阅读(451)  评论(0编辑  收藏  举报