• [正睿集训2021] 模拟赛1


    令牌生成

    题目描述

    (F(q)) 表示小于等于 (q) 的数中可以表示成 (2^x-2^y) 形式的正整数的个数,其中 (x,y) 都是非负整数。

    考虑将所有 (F(q)=n)(q) 按照二进制表示中的 (1) 个数排序,如果有 (1) 数量相等的按照原数大小排序。

    在排好序的序列中选取第 (k) 个数对 (998244353) 取模的结果作为令牌,如果序列的长度小于 (k),输出 (-1) 表示获取令牌失败。给定 (n,k) 要求回答多次询问。

    (1leq n,kleq 10^{18},1leq Tleq 100000)

    解法

    你看着这道题好像很牛皮的样子,其实就是一个大模拟。

    考虑 (2^x-2^y) 的数长成什么样子,肯定是前面一堆 (1),后面接的全是 (0)

    [111100000 ]

    [111110000 ]

    那么答案一定是夹在上面两个数之间,这两个数是可以算出来的。

    然后考虑在后面的 (cnt)(0) 里面填 (1),如果填入 (i)(1),那么方案数是 (C(cnt,i)),可以证明只用求 (O(log k)) 次组合数就可以得到足够的数,小于 (1e3) 的组合数可以预处理,剩下的组合数暴力算不会用多少时间。

    然后考虑 (1) 填的位置,可以二分 (1) 的位置 (l),那么方案数是 (C(l,...)),这个部分也是 (O(log^2 k)) 的吧。

    题目描述

    给定 (n) 个串,有 (q) 次询问,每次询问两个串的最长公共子串。

    (1leq nleq 50000)(1leq qleq 100000),串的总长小于 (50000)

    解法

    首先求两个串的最长公共子串是 (O(min(len_x,len_y))) 的,把小的子串丢进大的里面匹配就可以了。

    然后我用到了三元环计数的思想,把长度大于 (sqrt n) 的串称为大串,否则称为小串,分别来算贡献:

    • 对于小串,至多 (q) 次询问,那么时间复杂度 (O(qsqrt n))
    • 对于大串,它产生贡献的时候当且仅当遇到了大串,至多产生 (O(sqrt n)),那么它的复杂度是 (O(sqrt ncdot m_i)),又因为 (sum m_ileq n),时间复杂度 (O(nsqrt n))

    写的时候注意要记忆化时间复杂度才是真的。

    #include <cstdio>
    #include <vector>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 100005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,ans[M];vector<int> s[M];char st[M];
    struct ask
    {
    	int x,id;
    	ask(int X=0,int I=0) : x(X) , id(I) {}
    	bool operator < (const ask &R) const
    	{
    		return x<R.x;
    	}
    };vector<ask> q[M];
    struct node
    {
    	int fa,len,ch[26];
    };
    struct Sam
    {
    	int cnt,last;node a[2*M];
    	void init()
    	{
    		for(int i=1;i<=cnt;i++)
    		{
    			a[i].fa=a[i].len=0;
    			for(int j=0;j<26;j++)
    				a[i].ch[j]=0;
    		}
    		cnt=last=1;
    	}
    	void add(int c)
    	{
    		int p=last,np=last=++cnt;
    		a[np].len=a[p].len+1;
    		for(;p && !a[p].ch[c];p=a[p].fa) a[p].ch[c]=np;
    		if(!p) a[np].fa=1;
    		else
    		{
    			int q=a[p].ch[c];
    			if(a[p].len+1==a[q].len) a[np].fa=q;
    			else
    			{
    				int nq=++cnt;a[nq]=a[q];
    				a[nq].len=a[p].len+1;
    				a[q].fa=a[np].fa=nq;
    				for(;p && a[p].ch[c]==q;p=a[p].fa) a[p].ch[c]=nq;
    			}
    		}
    	}
    	int work(int t)
    	{
    		int ans=0,p=1,len=0;
    		for(int i=0;i<s[t].size();i++)
    		{
    			int c=s[t][i];
    			while(a[p].fa && !a[p].ch[c])
    			{
    				p=a[p].fa;
    				len=a[p].len;
    			}
    			if(a[p].ch[c])
    			{
    				p=a[p].ch[c];
    				len++;
    			}
    			ans=max(ans,len);
    		}
    		return ans;
    	}
    }A;
    int main()
    {
    	n=read();m=read();
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%s",st);
    		int len=strlen(st);
    		for(int j=0;j<len;j++)
    			s[i].push_back(st[j]-'a');
    	}
    	for(int i=1;i<=m;i++)
    	{
    		int x=read()+1,y=read()+1;
    		if(s[x].size()>s[y].size()) swap(x,y);
    		q[y].push_back(ask(x,i));
    	}
    	for(int i=1;i<=n;i++)
    	{
    		A.init();
    		for(int j=0;j<s[i].size();j++)
    			A.add(s[i][j]);
    		sort(q[i].begin(),q[i].end());
    		for(int j=0;j<q[i].size();j++)
    		{
    			int x=q[i][j].x,id=q[i][j].id;
    			ans[id]=A.work(x);
    			while(j<q[i].size()-1 && q[i][j+1].x==x)
    			{
    				ans[q[i][j+1].id]=ans[id];
    				j++;
    			}
    		}
    	}
    	for(int i=1;i<=m;i++)
    		printf("%d
    ",ans[i]);
    }
    

    随机游走

    题目描述

    (n) 个点,在点 (i) 上走一步的到的点是 (f_i),每一轮有 (p_j) 的概率走 (jin[0,m)) 步,一开始在每个点的概率都是一样的,求最后到达所有点的概率分别是多少,答案模 (998244353)

    (1leq nleq60000,1leq mleq 100000,kleq1000)

    部分分:(f) 是一个排列

    解法

    不难想到一个暴力 (dp),设 (dp[i][j]) 为第 (j) 轮在位置 (i) 的方案数,(g(i,j)) 表示 (i)(j) 步到的位置,那么有转移:

    [dp[g(i,k)][j+1]=dp[i][j] imes g_k ]

    这个 (dp) 难以优化,先讲一下部分分怎么做吧(虽然和正解没有关系),我们把 (f) 看成边连起来,因为它是排列所以最后得到了若干个置换环,环内每个点是等价的,那么无论怎么走最后得到的概率都是初始的概率,输出 (n) 的逆元就可以了。

    正解是生成函数,首先我们要把问题转化为环套树的图,一个点走多次之后会在环里面一直转,设 (p(x)) 是走 (x) 步概率的生成函数,求出一开始给定 (p(x))(k) 次幂即可。

    但是这样项数会很多,注意到会有循环,所以对于长度为 (s) 这一类 的环,设最大环套数的大小是 (n),那我们留足 (n) 项就可以了,这一类环套树的 (p(x)) 就可以一起处理,把环套树按大小排序就很容易算他们的生成函数了。根据简单的数学知识可知环套树的种类不超过 (sqrt n),此部分时间复杂度 (O(msqrt n+nlog nlog k))

    对于一个环套树,我们随便选一个环上的点作为根,它连出去的那条边叫非树边。我们先断开这条边建出一棵树,然后分别统计经过非树边和不经过非树边的贡献,图画出来是这个样子,我们顺便给每个点标上深度(正负都没关系):

    对于 不经过非树边的贡献 ,一个点仅可能由其子树中的点走到,而子树中点的 ( t dfs) 序又是一段连续的区间,我们把原图按照 ( t dfs) 序分块,我们预处理出整块和 (p(x)) 的卷积,零散块可以暴力算,时间复杂度 (O(nsqrt{nlog n}))

    对于 经过非树边的贡献 ,设 (H(x)=sum_{i=0}^nh_ix^{n-i}),其中 (h_i) 表示深度为 (-i) 的结点有多少个乘上初始概率,那么我们把 (h_i)(p_i) 求卷积就可以得到最终到环上某点的概率。因为有一些非环上的点也可能混在求出的概率里面,所以减去他们的概率即可。

    毒瘤题太难写了,所以没有代码。

  • 相关阅读:
    10.1~10.15学习情况
    ACM-ICPC 2018 沈阳赛区网络预赛
    打卡4
    打卡3
    tab 简单的tab
    css 圆形动画
    pdf和图片之间的转换
    对list进行分组
    C# 打开所在文件夹
    读取xml文件
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14415415.html
Copyright © 2020-2023  润新知