zoukankan      html  css  js  c++  java
  • 郑州集训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
    ",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
    ",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),表示存印器刚开始被存入了这个权值

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

  • 相关阅读:
    算法:合并排序(Merge Sort)
    安全:Web 安全学习笔记
    算法:冒泡排序(Bubble Sort)、插入排序(Insertion Sort)和选择排序(Selection Sort)总结
    算法:四种冒泡排序(Bubble Sort)实现
    算法:阶乘的五种算法
    算法:递归知识文章汇总
    算法:插入排序(Insertion Sort)
    .NET:线程本地存储、调用上下文、逻辑调用上下文
    算法:Rate of Growth
    企业应用:一个够用的、通用的状态机(管理实体的业务状态)
  • 原文地址:https://www.cnblogs.com/asuldb/p/10324488.html
Copyright © 2011-2022 走看看