机房模拟日常20181019

又双叒叕地考试.....

还有4道题...............

T1

AC自动机fail指针重构图然后dp即可(玩儿掉了自己的满分...........) 

我们将n个串建成一颗trie树。

然后我们定义一下f[i][j]表示长度为i的,以trie树上的节点j为结尾的“不好”的串(因为我们可以用总串数减去不好的串数就得到的好的串树)

然后方程变得非常简单

当以j为结尾的节点不是n个单词的末单词的时候,就有:

f[i][nxt[j]]+=f[i][j]

初值为f[0][0]=1;

总串数为26^m

减去sigma(d[m][i]),i从1到总节点数

作为T1好像有点难?

其实还OK23333

不过好像这道题是基础,因为状态转移方程是相同的,所以应该可以矩阵快速幂来着

随便写一写就A了。

但是考场上把自己玩儿脱了............

于是WA烂掉了

#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define OP "text"
const int MOD=12345;
const int MAXN=6005;
const int NN=1e5+2;
int n,m;
class Node
{
	public:
		int fail;
		int nxt[28];
		bool flag;
		void init(void);
}node[MAXN];
void Node::init()
{
	for(int i=0;i<28;i++)
	{
		nxt[i]=0;
	}
	flag=0;
	fail=0;
}
int tot;
int newnode()
{
	tot++;
	node[tot].init();
	return tot;
}
char s[MAXN];
void insert(char *s)
{
	int nd=0;
	for(char *it=s+1;*it;it++)
	{
		int x=*it-'A'+1;
		if(!node[nd].nxt[x]) 
		{
			node[nd].nxt[x]=newnode();
		}
		nd=node[nd].nxt[x];
	}
	node[nd].flag=1;
}
int f[105][MAXN];
void getfail()
{
	std::queue<int>q;
	for(int i=1;i<=26;i++)
	{
		if(node[0].nxt[i])
		{
			q.push(node[0].nxt[i]);
		}
	}
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=1;i<=26;i++)
		{
			if (node[u].nxt[i])
			{
				q.push(node[u].nxt[i]);
				node[node[u].nxt[i]].fail=node[node[u].fail].nxt[i];
			} 
			else 
			{
				node[u].nxt[i]=node[node[u].fail].nxt[i];
			}
		}
		node[u].flag|=node[node[u].fail].flag;
	}
}
int solve()
{
	getfail();
	f[0][0]=1;
	for(int i=1;i<=m;i++)
	{
		for(int j=0;j<=tot;j++)
		{
			if(node[j].flag)
			{
				continue;
			}
			for(int k=1;k<=26;k++)
			{
				f[i][node[j].nxt[k]]=(f[i][node[j].nxt[k]]+f[i-1][j])%MOD;
			}
		}
	}
	int R=1;
	for(int i=1;i<=m;i++)
	{
		R=1ll*R*26%MOD;
	}
	int L=0;
	for(int i=0;i<=tot;i++)
	{
		if(node[i].flag)
		{
			continue;
		}
		L=(1ll*L+f[m][i])%MOD;
	} 
	return (R-L%MOD+MOD)%MOD;
}
int main()
{
	std::freopen(OP".in","r",stdin);
	std::freopen(OP".out","w",stdout);
	std::scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		std::memset(s,0,sizeof s);
		std::scanf("%s",s+1);
		insert(s);
	}
	std::printf("%d\n",solve());
	return 0;
}

T2

 

 

 当天的也许最难的题............

这道题的正解是时间分治(离线做法)

又强制在线的做法(LCT各种乱搞....loj上看到了一个25KB的代码吓惨了.......)

我们将m个询问建立一颗线段树

然后对于每一次加边的询问,就将这个边的点记录下来,然后再之后删除这条边的时候,将这段区间插入这一条边。

在插入一条边的时候当且仅当当前区间的时间将该询问完全覆盖的时候进行插入

然后我们就会发现,从根节点走到一个叶子节点的路径上的所有边就是单次询问的时候所有的联通的点

然后直接查询就好了

但是退出去的时候需要把这些边的信息赋值回去,否则的话图的连通性就完全混乱了

所以并查集的时候启发式合并而不压缩路径是很完美的选择(不启发式合并会T得很惨烈)

lemon上A得飞快结果cena上死也A不了,尬得要死........

T3

双向dfs模版题.......

先搜索得出前面N/2的状态,然后用一个map将其权值和一个vector将所有权值的状态存下来

然后搜索得到后面N/2的状态,然后用再搜到N的时候再vector中查询权值相同的状态并打上标记

然后搜索一遍所有的状态,每当有一个状态打上标记ans就+1

好的过掉~~~~~

T4

 

数论推个P,打表找规律系列

直接打表发现ans=(C(2*n,n)+(n*p*(1-p))

预处理组合数取一个模就过掉了

#include<cstdio>
#include<algorithm>
#define LL long long
#define OP "mathematics"
const int MAXN=2e6+3;
const int MOD=1e9+7;
void exgcd(int a,int b,LL &x,LL &y)
{
	if(!b)
	{
		x=1;
		y=0;
		return;
	}
	else 
	{
		exgcd(b,a%b,y,x);
		y-=(a/b)*x;
	}
}
LL inverse(int a,const int MOD)
{
	LL x,y;
	exgcd(a,MOD,x,y);
	return (x%MOD+MOD)%MOD;
}
LL fac[MAXN<<1];
LL inv[MAXN];
void init()
{
	fac[0]=1;
	for(int i=1;i<=4000000;i++)
	{
		fac[i]=fac[i-1]*i%MOD;
	}
	inv[0]=1;
	inv[2000000]=inverse(fac[2000000],MOD);
	for(int i=2000000-1;i>=1;i--)
	{
		inv[i]=1ll*(i+1)*inv[i+1]%MOD;
	}
}
LL C(int n,int m)
{
	if(n<m)
	{
		return 0;
	}
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int main()
{
	std::freopen(OP".in","r",stdin);
	std::freopen(OP".out","w",stdout);
	init();
	int T;
	std::scanf ("%d",&T);
	while(T--)
	{
		int n,p;
		std::scanf("%d%d",&n,&p);
		LL ans=(C(n<<1,n)%MOD+(1LL*n*p%MOD*(1LL-p)%MOD+MOD)%MOD)%MOD;
		if(ans<0)
		{
			ans+=MOD;
		}
		std::printf("%lld\n",ans);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Amuseir/article/details/83182730