zoukankan      html  css  js  c++  java
  • [正睿集训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) 求卷积就可以得到最终到环上某点的概率。因为有一些非环上的点也可能混在求出的概率里面,所以减去他们的概率即可。

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

  • 相关阅读:
    django页面分类和继承
    django前端从数据库获取请求参数
    pycharm配置django工程
    django 应用各个py文件代码
    CF. 1428G2. Lucky Numbers(背包DP 二进制优化 贪心)
    HDU. 6566. The Hanged Man(树形背包DP DFS序 重链剖分)
    小米邀请赛 决赛. B. Rikka with Maximum Segment Sum(分治 决策单调性)
    区间树 学习笔记
    CF GYM. 102861M. Machine Gun(主席树)
    2016-2017 ACM-ICPC East Central North America Regional Contest (ECNA 2016) (B, D, G, H)
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14415415.html
Copyright © 2011-2022 走看看