• Deltix Round, Spring 2021


    D. Love-Hate

    题目描述

    点此看题

    (n) 个长度为 (m) 的二进制串,为 (1) 的串至多有 (p) 个。

    试从中选出 (lceilfrac{n}{2} ceil) 个串,使得他们并集为 (1) 个位个数最大。

    (nleq 2cdot 10^5,mleq 60,pleq 15)

    解法1

    暴力出奇迹!我们搜索可能的答案,计算有多少个串能选进去。

    每次加入可能成为答案的数位(至少出现 (lceilfrac{n}{2} ceil) 次),然后用 ( t bitset) 维护能选的串的数量。

    解法2

    一个更加稳定的做法,我们考虑充分利用选串个数为 (lceilfrac{n}{2} ceil) 这个条件,考虑对于一个串他有 (frac{1}{2}) 的概率不出现在答案里面,而如果一个串出现在答案里面我们一定能用它算出答案,答案一定是他的子集。

    那么我们随机 (20) 个下标,就有 ((frac{1}{2})^{20}) 的概率得到正确答案。

    确定下标之后就好做了,我们看有多少个人的子集是 (s),可以子集枚举 (O(3^p+pcdot n)) 算出。

    你发现这是个 ( t FWT) 正变换的过程,所以也可以 (O(pcdot 2^p+pcdot n)) 算出。

    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <vector>
    using namespace std;
    const int M = 200005;
    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,p,mx,ans[65],a[M][65],bit[1<<15],cnt[1<<15];
    int random(int x)
    {
    	return (rand()*rand()+rand())%x;
    }
    void work(int x)
    {
    	vector<int> v;
    	for(int i=1;i<=m;i++)
    		if(a[x][i]) v.push_back(i);
    	memset(cnt,0,sizeof cnt);
    	for(int i=1;i<=n;i++)
    	{
    		int msk=0;
    		for(int j=0;j<v.size();j++)
    			if(a[i][v[j]]) msk|=(1<<j);
    		cnt[msk]++;
    	}
    	for(int i=0;i<(1<<p);i++)
    		for(int j=i;;j=(j-1)&i)
    		{
    			if(i!=j) cnt[j]+=cnt[i];
    			if(j==0) break;
    		}
    	for(int i=0;i<(1<<p);i++)
    		if(cnt[i]>=(n+1)/2 && bit[i]>mx)
    		{
    			mx=bit[i];
    			memset(ans,0,sizeof ans);
    			for(int j=0;j<p;j++)
    				if(i&(1<<j)) ans[v[j]]=1;
    		}
    }
    signed main()
    {
    	n=read();m=read();p=read();
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=m;j++)
    			scanf("%1d",&a[i][j]);
    	}
    	for(int i=1;i<(1<<p);i++)
    		bit[i]=bit[i>>1]+(i&1);
    	for(int i=1;i<=20;i++)
    	{
    		int x=random(n)+1;
    		work(x);
    	}
    	for(int i=1;i<=m;i++)
    		printf("%d",ans[i]);
    }
    

    E.Crypto Lights

    题目描述

    点此看题

    (n) 栈灯,每次随机选择一盏未点亮的灯点亮,如果存在连续 (k) 栈灯亮了两个及以上就跳出,求点亮灯个数的期望。

    (2leq kleq nleq 10^5)

    解法

    好久没做过这么纯粹的期望题了,但是不会

    拆贡献是算期望最重要的方法,具体的,对于本题我们计算点亮 (i) 栈灯,并且当前状态合法的概率,对所有 (i) 的概率求和 (+1) 就是期望。

    可以先算方案数,我们选择 (i) 个灯亮,因为不能把不合法的方案算进去,所以我们计算方式是先强制每两个 (1) 中有 (k-1)(0),然后剩下的 (0) 任意插入 (1) 的间隙中,这相当于 (n-(i-1)(k-1)) 个位置中选择 (i)(1),即 ({n-(i-1)(k-1)choose i})

    因为有顺序问题所以还要略微修正一下,因为这 (i) 个灯可以任意顺序点亮所以要乘上 (i!),因为我们的考虑不是完全的,所以每一种方案实际上代表着 ((n-i)!) 种最终方案。即使有些方案会中途夭折,我们还是可以把最终总方案当成 (n!)只不过有些方案的贡献计算变化罢了,所以要乘上 ((n-i)!) 再除以 (n!)

    [ans=1+frac{1}{n!}sum_{i=1}^n{n-(i-1)(k-1)choose i}cdot i!cdot (n-i)! ]

    #include <cstdio>
    const int M = 100005;
    const int MOD = 1e9+7;
    #define int long long
    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 T,n,k,ans,fac[M],inv[M];
    void init(int n)
    {
    	fac[0]=inv[0]=inv[1]=1;
    	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
    	for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
    	for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
    }
    int C(int n,int m)
    {
    	if(n<m) return 0;
    	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
    }
    signed main()
    {
    	T=read();init(1e5);
    	while(T--)
    	{
    		n=read();k=read();ans=0;
    		for(int i=1;i<=n;i++)
    			ans=(ans+C(n-(i-1)*(k-1),i)*fac[i]%MOD*fac[n-i])%MOD;
    		ans=(ans*inv[n]+1)%MOD;
    		printf("%lld
    ",ans);
    	}
    }
    

    F. Favorite Game

    题目描述

    点此看题

    在二维平面内,有 (n) 个传送塔和 (m) 个地标,初始时阿七可以降落在任意地点,每秒可以选择不动,或者向四周移动一步。如果阿七到过某个传送塔,就会将其激活,以后可以瞬间移动到这个传送塔的位置。

    每个地标有指定地点和指定时间 (t_i),如果阿七正好在 (t_i) 时间到达这个地标就可以完成一个刺杀任务,最大化任务完成数。

    (nleq 14,mleq 100,1leq x,yleq 10^6,1leq t_ile10^9)

    解法

    首先把所有地标按指定时间升序排序,这样就确定了 (dp) 的转移顺序。

    可以定义出最朴素的状态,设 (dp(s,i,j)) 表示阿七到达的传送塔集合状压为 (s),任务完成总数为 (i),现在的地点是 (j)(可能是地标也可能是传送塔)的最小消耗时间,但是状态数就有了 (O(2^nm^2)) 直接起飞,我们不妨来搞一些 ( t observations)

    • 重要的量有四个:到达的传送塔集合,任务完成总数,现在所处地点,当前时间,看上去三维 (dp) 是必须的。
    • 如果阿七在传送塔,因为可以瞬移,所以我们不用关心阿七在哪个传送塔,那么就只剩了三个量。
    • 如果阿七在地标,因为刺杀任务的时间指定是 (t_i),所以我们不用关心时间,那么也只剩了三个量。
    • 所有的情况可以分为阿七在传送塔和阿七在地标。

    综合以上观察,我们得到了优化的思路:通过类似分类讨论的方法来减少状态量,给 (dp) 降维

    上面是我自己的思考,现在就能顺理成章地得到题解的做法:设 (f[s][i]) 表示到达的传送塔集合为 (s),任务完成总数为 (i),现在处于传送塔的最小时间;设 (g[s][i]) 表示到达传送塔集合为 (s),现在处于地标 (i) 的最大任务完成总数,来写转移:

    • (w[s][i]) 表示到达传送塔集合为 (s),从某个传送塔到地点 (i) 的最小时间,这个可以预处理方便转移。
    • 从某个传送塔出发到一个新的传送塔:(f[soplus j][i]leftarrow f[s][i]+w[s][j])
    • 从某个传送塔出发到一个新的地标:(g[s][j]leftarrow i+1),当满足 (f[s][i]+w[s][j]leq t[j]) 时转移
    • 从地标出发到一个新的传送塔:(f[soplus j][g[s][i]]leftarrow t[i]+min(w[s][j],dis[i][j]))
    • 从地标到下一个地标:(g[s][j]leftarrow g[s][i]+1),当满足 (min(w[s][j],dis(i,j))+t[i]leq t[j]) 时转移

    时间复杂度 (O(2^nm^2)),有一些小细节可以参考代码注释。

    #include <cstdio>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 120;
    const int N = 1<<14;
    const int inf = 0x3f3f3f3f;
    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,f[N][M],g[N][M],w[N][M];
    //f[s][i]:stand for having reached tower s,visited i landmarks,now in a tower
    //g[s][i]:stand for having reached tower s,now in a landmark
    struct node
    {
    	int x,y,t;
    	bool operator < (const node &r) const
    	{
    		return t<r.t;
    	}
    }a[M];
    int Abs(int x) {return x>0?x:-x;}
    int get(node a,node b)
    {
    	return Abs(a.x-b.x)+Abs(a.y-b.y);
    }
    signed main()
    {
    	n=read();m=read();
    	for(int i=0;i<n;i++)
    		a[i].x=read(),a[i].y=read();
    	for(int i=n;i<n+m;i++)
    		a[i].x=read(),a[i].y=read(),a[i].t=read();
    	sort(a+n,a+n+m);
    	for(int s=0;s<(1<<n);s++)
    	{
    		for(int i=0;i<n+m;i++)
    		{
    			f[s][i]=w[s][i]=inf;g[s][i]=-inf;
    			for(int j=0;j<n;j++)
    				if(s&(1<<j))
    					w[s][i]=min(w[s][i],get(a[i],a[j]));
    		}
    	}
    	for(int i=0;i<n;i++) f[1<<i][0]=0;
    	for(int i=0;i<m;i++) g[0][i]=1;
    	for(int s=0;s<(1<<n);s++)
    	{
    		//transitions with f
    		for(int i=0;i<=m;i++)
    			if(f[s][i]<inf)
    			{
    				for(int j=0;j<n;j++)
    					if(!(s&(1<<j)))
    						f[s|(1<<j)][i]=
    						min(f[s|(1<<j)][i],f[s][i]+w[s][j]);
    				for(int j=0;j<m;j++)//j will not be a landmark that is visited 
    					if(f[s][i]+w[s][j+n]<=a[j+n].t)
    						g[s][j]=max(g[s][j],i+1);
    			}
    		//transitions with g
    		for(int i=0;i<m;i++)
    			if(g[s][i]>=0)
    			{
    				for(int j=0;j<n;j++)
    					if(!(s&(1<<j)))
    						f[s|(1<<j)][g[s][i]]=min(f[s|(1<<j)][g[s][i]]
    						,a[i+n].t+min(get(a[i+n],a[j]),w[s][j]));
    				for(int j=i+1;j<m;j++)//to guarantee j is not visited
    					if(min(get(a[i+n],a[j+n]),w[s][j+n])+a[i+n].t<=a[j+n].t)
    						g[s][j]=max(g[s][j],g[s][i]+1);
    				ans=max(ans,g[s][i]);
    			}
    	}
    	printf("%d
    ",ans);
    }
    

    G. Try Booking

    题目描述

    点此看题

    大保( t J)发廊近期的生意火爆,对于 (n) 天的营业期,大保收到了 (m) 个订单,表示在 ([l_i,r_i]) 让阿七剪头发。大保会按收到订单的顺序处理,如果当前订单和以前订单没有冲突,就接这个订单。

    大保想知道如果忽略 (r_i-l_i+1<x) 的订单最后阿七的营业天数是多少,对于所有 (xin[1,n]) 都要求出答案。

    (nleq 5cdot 10^4,mleq 10^5)

    解法

    我真的没观察出什么性质,当前订单的选取对后面的影响实在是太大了。

    有一个重要的 ( t observation):如果我们每次都能直接找到下一个接的订单,那么总共接订单的数量是 (sum_{i=1}^nfrac{n}{i}=O(nlog n))

    也就是说只要我们能快速找到下一个订单是谁复杂度就对了,由于是一个二维偏序问题,那么直接上一个树套树即可,知道订单之后就把当前区间分成两半递归下去,相当于一个分治查的过程。

    时间复杂度 (O(nlog^3n)),原谅我没有写代码。

    H. Hopping Around the Array

    题目描述

    点此看题

    小飞在长度为 (n) 的数组上面玩游戏,位置 (i) 有一个跳跃距离 (a_i),小飞可以选择通过一步跳跃降落到 ((i,i+a_i]) 的任意一个地点。但是它觉得直接跳太累了,所以阿七可以帮助它移走任意 (k) 个位置,剩下的位置会重新编号,但是 (a) 不会改变。

    一共有 (q) 次游戏,每次从 (l) 处出发,(r) 处结束,能移走 (k) 个位置,但是不能移走起点和终点。作为世界上最强的飞鸡,小飞想知道最小跳跃次数。

    (1leq n,qleq 20000,1leq lleq rleq n,0leq kleq min(30,r-l))

    解法

    首先考虑一个简化的问题,如果没有移走位置的操作怎么最小化飞行次数。

    你肯定会说 (dp),这固然很好,但是这种方法无法扩展到本题的情况。不妨贪心一点考虑,肯定不能无脑跳最远的地方,但是我们可以让我们的跳跃范围最远,也就是我们找到 (a_i+i) 最大的位置直接跳它,这种贪心在最后一步的跳远除外都是使用的,而且它的可扩展性显然优于 (dp)

    然后考虑有了移走操作怎么办,移走操作的本质是增加跳跃范围,也就是说只有你想跳到 (a[i]+i+x) 的时候你才会移走 (x) 个格子,否则无需移走格子也能跳到你想要的地方。

    如果询问只有一组现在是可以解决的,但是本题是多组询问。回忆我省选的惨痛经历,这种既有步数移动又是多组询问的问题是和倍增很搭的,因为 (k) 很小而且需要分配移动操作所以可以把他加入状态中,那么不难定义出倍增数组 (f[i][j][k]) 表示从位置 (i) 开始跳 (2^j) 步,移走 (k) 个格子能到达的最远范围

    考虑转移,首先要枚举前一半用掉的删除次数是 (x),那么用 (st) 表找到 ([i,f[i][j-1][x]]) 这个范围中最大 (a_p+p)(p),然后就可以从 (p) 完成后半段的跳跃,最远的范围就是 (f[p][j-1][k-x]),这样就完成了 (f[i][j][k]) 的转移。我们初始化 (f[i][0][x]=i+a[i]+x) 即可。

    现在考虑怎么用这个倍增数组回答询问,大体的思路是用 ( t lca) 后半段的那种方法,我们多跳 (2^x) 看会不会跳到 (r),如果没有跳到 (r) 了那么就把 (2^j) 计入 (ans),最后把 (ans+1) 输出即可。

    由于我们要分配移动次数所以我们定义 (g[i]) 表示从 (l) 开始跳若干步,用掉的移动次数是 (i) 的最远范围,转移就枚举新跳的 (2^x) 步中用掉了移动次数 (j),找到 ([l,g[i]]) 中最大 (p+a_p)(p),用 (f[p][x][j]) 转移到辅助数组 (tmp[i+j]) 中即可。

    注意要限制不能跳出 (n),要不然下标会有问题,时间复杂度 (O((n+q)cdot k^2cdot log n))

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int M = 20005;
    const int N = 31;
    #define make make_pair
    #define pii pair<int,int>
    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,q,a[M],lg[M],f[M][20][N],g[N],tmp[N];pii dp[M][20];
    //f[i][j][k]:stand for the max (l+al) can reach from i through 2^j jumps and k delete 
    int ask(int l,int r)//ask the l of max (l+al) 
    {
    	int k=lg[r-l+1];
    	return max(dp[l][k],dp[r-(1<<k)+1][k]).second;
    }
    void init()
    {
    	for(int j=1;(1<<j)<=n;j++)//st
    		for(int i=1;i+(1<<j)-1<=n;i++)
    			dp[i][j]=max(dp[i][j-1],dp[i+(1<<j-1)][j-1]);
    	for(int i=1;i<=n;i++)
    		for(int j=0;j<N;j++)
    			f[i][0][j]=min(n,i+a[i]+j);//this "min" is necessary
    	for(int j=1;(1<<j)<=n;j++)
    		for(int i=1;i<=n;i++)//please don't limit the range of i
    			for(int k=0;k<N;k++)
    				for(int x=0;x<=k;x++)
    				{
    					int t=ask(i,f[i][j-1][x]);//find the position
    					f[i][j][k]=max(f[i][j][k],f[t][j-1][k-x]);
    				}
    }
    signed main()
    {
    	n=read();q=read();
    	for(int i=1;i<=n;i++)
    	{
    		a[i]=read();
    		dp[i][0]=make(min(n,a[i]+i),i);
    		if(i>1) lg[i]=lg[i>>1]+1;
    	}
    	init();
    	while(q--)
    	{
    		int l=read(),r=read(),k=read(),ans=0;
    		if(l==r) {puts("0");continue;}
    		if(l+a[l]+k>=r) {puts("1");continue;}
    		for(int i=0;i<=k;i++) g[i]=l;
    		for(int x=15;x>=0;x--)
    		{
    			if((1<<x)>n) continue;
    			for(int i=0;i<=k;i++) tmp[i]=g[i];
    			for(int i=0;i<=k;i++)
    			{
    				int t=ask(l,g[i]);
    				for(int j=0;i+j<=k;j++)
    					tmp[i+j]=max(tmp[i+j],f[t][x][j]);
    			}
    			int fl=0;
    			for(int i=0;i<=k;i++)
    				if(tmp[i]>=r) fl=1;
    			if(fl) continue;
    			for(int i=0;i<=k;i++) g[i]=tmp[i];
    			ans+=(1<<x);
    		}
    		printf("%d
    ",ans+1);
    	}
    }
    
  • 相关阅读:
    PTA数据结构与算法题目集(中文) 7-19
    PTA数据结构与算法题目集(中文) 7-18
    PTA数据结构与算法题目集(中文) 7-16
    PTA数据结构与算法题目集(中文) 7-15
    PTA数据结构与算法题目集(中文) 7-14
    PTA数据结构与算法题目集(中文) 7-11
    PTA数据结构与算法题目集(中文) 7-10
    PTA数据结构与算法题目集(中文) 7-9
    PTA数据结构与算法题目集(中文) 7-8
    Executor线程池原理详解(一)
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14842025.html
Copyright © 2020-2023  润新知