• Codeforces Round #752 (Div. 1)


    前言

    比赛之前我就想着先开 (D),然后肝了 (1.8) 个小时终于搞出来了,因为我是怂包所以不敢用大号交,用小号抢了 ( t Div2F) 的首 (A)(好像赛时很少人做出来),就不想打了。

    下次还是要相信自己的实力,自信即颠峰,(3000) 的题我不只切了一次两次了。不要畏惧难题,一发上红不是问题。

    C. Extreme Extension

    题目描述

    定义操作为把数 (a) 拆成两个和等于 (a) 的正整数,定义序列的价值为最小的操作次数使之不降。

    (n) 个数 (a_i),问每一个子区间的价值和,答案对 (998244353) 取模。

    (nleq 10^5,a_ileq 10^5)

    解法

    首先考虑对于一个给定的区间如何计算价值。可以从后往前考虑,每一次都尽量大的拆分,这样的操作数是最小的,并且于后面也是最优的。设现在的开头是 (x),那么个数 (k=lceilfrac{a_i}{x} ceil),新的开头 (x'=lfloorfrac{a_i}{k} floor)

    对于每个 (i)(k) 的取值只有 (sqrt n) 种,那么 (x') 的取值也只有 (sqrt n) 种。因为计算只和开头的数字有关,可以把它当成状态记录下来,我们使用整体 (dp) 的技巧,也就是在所有右端点处初始化,在左端点处统计答案,只需要做一次 (dp) 就可以解决问题。

    (dp[i][j]) 表示考虑到 (i) 开头的数字是 (j) 的方案数,时间复杂度 (O(nsqrt n))

    总结

    区间统计问题也可以考虑整体 (dp) 的技巧,考虑需要记录什么东西把统计问题转 (dp)

    #include <cstdio>
    #include <vector>
    #include <iostream>
    using namespace std;
    const int M = 100005;
    const int MOD = 998244353;
    #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,w,ans,a[M],f[2][M];vector<int> v[2];
    signed main()
    {
    	T=read();
    	while(T--)
    	{
    		n=read();w=ans=0;
    		for(int i=1;i<=n;i++)
    			a[i]=read();
    		for(int i=n;i>=1;i--)
    		{
    			w^=1;int ls=a[i];
    			v[w].push_back(a[i]);
    			f[w][a[i]]=1;
    			for(auto x:v[w^1])
    			{
    				int k=(a[i]+x-1)/x,to=a[i]/k;
    				f[w][to]=(f[w][to]+f[w^1][x])%MOD;
    				ans=(ans+(k-1)*i*f[w^1][x])%MOD;
    				if(to^ls) v[w].push_back(to),ls=to;
    			}
    			for(auto x:v[w^1]) f[w^1][x]=0;
    			v[w^1].clear();
    		}
    		for(auto x:v[w]) f[w][x]=0;v[w].clear();
    		printf("%lld
    ",ans);
    	}
    }
    

    D. Artistic Partition

    题目描述

    定义 (c(l,r)) 表示满足 (lleq ileq jleq r) 并且 (gcd(i,j)geq l)((i,j)) 对数。

    定义 (f(n,k)) 为一个划分 (0=x_1<x_2<...x_{k+1}=n) 最小的 (sum_{i=1}^k c(x_i+1,x_{i+1}))

    多组数据,求 (f(n,k))

    (Tleq 3cdot 10^5,1leq kleq nleq 10^5)

    解法

    首先我想了一个 (O(n^4k))(dp),打了个表发现没什么规律,然后开始疯狂优化。

    我发现 (k) 较小的时候答案就趋近 (n) 了,设左端点是 (l),那么 ([l,2l)) 这一段的额外代价为 (0),所以说最多只需要 ( t log) 次划分就可以使得答案达到最小值。

    再考虑怎么优化枚举前驱的过程,可以盲猜一波有决策单调性,也就是满足下面的式子:

    [c(i,j+1)+c(i+1,j)geq c(i+1,j+1)+c(i,j) ]

    经我打表验证加上感性理解,可以证明上面的式子的成立的,那么套一个决策单调性分治即可。剩下的问题就是快速求 (c(l,r)) 了,比赛时我就是因为这个卡了很久,其实直接莫比乌斯反演即可:

    [egin{aligned}c(l,r)&=sum_{i=l}^rsum_{j=i+1}^r[gcd(i,j)geq l]\&=sum_{k=l}^{r}sum_{i=l}^rsum_{j=i+1}^r[gcd(i,j)=k]\&=sum_{k=l}^{r}sum_{i=1}^{r/k}sum_{j=i+1}^{r/k}[gcd(i,j)=1]\&=sum_{k=l}^rsum_{i=1}^{r/l}phi(i)\&=sum_{k=l}^r sum(lfloorfrac{r}{i} floor)end{aligned} ]

    所以求出欧拉函数的前缀和之后整除分块即可,在一段转移区间内求值的时候只需要把左端点拿出去做整除分块,剩下的可以略微修改推出来,时间复杂度我也不太清楚,因为是预处理所以该算法稳定在 (1s) 左右。

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    using namespace std;
    const int M = 100005;
    #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,cnt,p[M],ph[M],dp[22][M];
    void init(int n)
    {
    	ph[1]=1;
    	for(int i=2;i<=n;i++)
    	{
    		if(!ph[i]) ph[i]=i-1,p[++cnt]=i;
    		for(int j=1;j<=cnt && i*p[j]<=n;j++)
    		{
    			if(i%p[j]==0)
    			{
    				ph[i*p[j]]=ph[i]*p[j];
    				break;
    			}
    			ph[i*p[j]]=ph[i]*(p[j]-1);
    		}
    	}
    	for(int i=1;i<=n;i++) ph[i]+=ph[i-1];
    }
    int cal(int m,int n)
    {
    	int res=0;
    	for(int l=m,r=0;l<=n;l=r+1)
    	{
    		r=n/(n/l);
    		res+=ph[n/l]*(r-l+1);
    	}
    	return res;
    }
    void cdq(int l,int r,int L,int R)
    {
    	if(l>r) return ;
    	int mid=(l+r)>>1,c=cal(L+1,mid),p=0;
    	for(int i=L;i<=min(R,mid);i++)
    	{
    		if(dp[k][mid]>dp[k-1][i]+c)
    		{
    			dp[k][mid]=dp[k-1][i]+c;
    			p=i;
    		}
    		c-=ph[mid/(i+1)];
    	}
    	cdq(l,mid-1,L,p);
    	cdq(mid+1,r,p,R);
    }
    signed main()
    {
    	n=100000;init(n);
    	memset(dp,0x3f,sizeof dp);dp[0][0]=0;
    	for(k=1;k<=20;k++) cdq(1,n,0,n-1);
    	T=read();
    	while(T--)
    	{
    		n=read();k=read();
    		if(k>20) printf("%lld
    ",n);
    		else printf("%lld
    ",dp[k][n]);
    	}
    }
    

    E. A Perfect Problem

    题目描述

    定义一个序列是好的,当且仅当最大值乘上最小值大于等于权值和。

    定义一个长度为 (n) 的序列 (a) 是完美的,当且仅当 (1leq a_ileq n+1),并且它的所有子序列都是好的。

    给出 (n) 和质数 (m),求长度为 (n) 的完美序列 (a) 有模 (m) 下有多少个。

    (nleq 200,10^8leq mleq 10^9)

    解法

    (cdot) 结论题,本题最关键的条件就是 (1leq a_ileq n+1)(这限制梦回 ( t NOI2020)

    ( t Observation one):可以贪心转化判据,我们把 (a) 序列从小到大排序,如果最大值是 (a_i) 那么最小值一定是 (a_1),那么所有子序列合法就转化成了所有前驱合法。

    ( t Observation two):我们可以只考虑排序之后的序列,然后用简单组合数就可以计算出原序列,排序后序列需要满足 (a_igeq i),否则 (a_icdot a_1<icdot a_ileq sum_{j=1}^i a_j) 显然不合法。

    ( t Observation three):如果 (a_i=i),那么 (forall jleq i,a_j=i),因为 (a_icdot a_1=icdot a_1leq sum_{j=1}^i a_j),所以当且仅当所有数等于 (i) 时取等,此时才能满足条件。

    基于这个观察和对题设的分析,我们可以知道当 (a_n=n) 的时候唯一对应一种合法序列 ,所以可以假设 (a_n=n+1) 以方便下面的讨论。

    ( t Observation four):假设 (a_n=n+1),如果 (a_igeq i+1),那么这个前缀自动合法。因为我们知道 (a_1cdot a_ngeqsum_{i=1}^na_i),所以 (a_1geq sum_{i=1}^n a_i-a_1),推出 (a_1geq sum_{j=1}^i a_j-a_1),所以前缀合法。


    综合上文所有的观察,我们可以知道假设 (a_n=n+1),序列合法的充要条件是:

    • (forall ileq a_1,a_1leq a_ileq n+1)
    • (forall i>a_1,i+1leq a_ileq n+1)
    • (sum_{i=1}^n a_i-a_1leq a_1)

    我们枚举 (a_1) 之后,可以去规划差值序列 (b_i=a_i-a_1),序列 (b) 合法的充要条件是:

    • (0leq b_ileq n+1-a_1)
    • (sum_{i=1}^n b_ileq a_1)
    • 至少有 (1) 个数大于等于 (n+1-a_1),至少有 (2) 个数大于等于 (n-a_1.....)至少有 (n-a_1) 个数大于等于 (2)

    (dp[i][j][k]) 表示考虑到权值 (k),已经在序列里放置了 (i) 个数,现在的总和是 (j),转移枚举新加数的权值有多少个,不难发现这是个调和级数,所以时间复杂度 (O(n^4log n))

    ( t Observation seven):有用的 (a_1) 只有后 (2sqrt n) 种,所以时间复杂度 (O(n^3sqrt nlog n))

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <cassert>
    using namespace std;
    const int M = 205;
    #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 n,MOD,a1,ans,fac[M],inv[M],f[M][M][M],v[M][M][M];
    int qkpow(int a,int b)
    {
    	int r=1;
    	while(b>0)
    	{
    		if(b&1) r=r*a%MOD;
    		a=a*a%MOD;
    		b>>=1;
    	}
    	return r;
    }
    int dfs(int i,int s,int k)
    {
    	if(i==n) return fac[n];
    	if(k==0) return fac[n]*inv[n-i]%MOD;
    	if(v[i][s][k]==a1) return f[i][s][k];
    	int &r=f[i][s][k];r=0;v[i][s][k]=a1;
    	for(int j=(a1-s)/k;j>=0;j--)
    	{
    		if(k>1 && i+j<n-a1+1-k+1) continue;
    		r=(r+dfs(i+j,s+j*k,k-1)*inv[j])%MOD;
    	}
    	return r;
    }
    signed main()
    {
    	n=read();MOD=read();fac[0]=1;
    	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
    	for(int i=0;i<=n;i++) inv[i]=qkpow(fac[i],MOD-2);
    	for(a1=max(1ll,n-30);a1<=n;a1++)
    		ans=(ans+dfs(0,0,n+1-a1))%MOD;
    	printf("%lld
    ",ans);
    }
    
  • 相关阅读:
    解决不同浏览器文件下载文件名乱码问题(Java)
    zabbix常见问题
    axiosapi,js结构化定义、调用业务api接口。 no
    Java基础:反射小结
    Java基础:函数式编程的函数的本质
    Java基础:Java方法的签名的定义
    Java基础:异常怎么分类的(面试题:Exception和Error的区别),看完这篇就都捋清了
    编程类型和代表性语言
    Java基础:构造器小结
    ubuntu搭建wifi热点,共享网络,超简单
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15492846.html
Copyright © 2020-2023  润新知