• 【洛谷7116】微信步数(自然数幂和)


    点此看题面

    • 有一个(k)维场地,第(i)维范围为([1,w_i])
    • (n)个操作,每个操作可以表示为将某一维的坐标(±1)
    • 你会先选定一个起点,然后不断重复依次执行这(n)个操作直至走出场地。
    • 场地中每个位置都会被作为一次起点,你想要知道从所有点出发执行操作次数的总和。
    • (nle5 imes10^5,kle10)

    真的想不到这次(NOIP)唯一一道做出来的题目居然是(T4)。。。

    暴力思路

    考虑对于每一个操作(i),我们统计以它为最后一个操作的总贡献。

    首先一个最基本的结论,各维之间的位移是独立的,因此我们的对象就是这维最先越界的点

    既然是暴力,那么我们就去枚举是在第(x+1)轮结束的,但发现第(1)轮的情况和之后的情况可能略有一些区别,需要单独讨论。

    这里先设(sum_t,smx_t,smn_t)分别表示执行到第(i)个操作时第(t)维的位移、历史最大位置、历史最小位置。

    然后类似地设(tot_t,Mx_t,Mn_t)分别表示执行完一轮操作之后第(t)维的位移、历史最大位移、历史最小位置。

    方便起见,我们强制所有(tot_tge0)。这只要在(tot_t<0)时把所有第(t)维的操作都取反就可以了。

    在第(1)轮结束

    首先考虑它是否可能作为一个结束操作。

    那么先要满足不存在某一维(t)使得(smx_t-smn_tge w_t),因为这就说明所有点都必然已经走出了。

    否则,还需要满足当前的(sum_{c_i})不在([smn_{c_i},smx_{c_i}])范围内,因为在这范围内意味着这个位移之前已经达到过,就不可能在这一次操作越界。

    如果这两个条件都满足了,就意味着这个操作可以作为结束操作,且因这个操作越界的点的第(c_i)维的坐标只有一种取值。

    接着考虑其他维度,对于第(t)维,已经有(smx_t-smn_t)个点越界了,那么还有(w_t-(smx_t-smn_t))种取值。

    所以方案数就是:

    [sum_{t ot=c_i}(w_t-(smx_t-smn_t)) ]

    在第(x+1)轮结束

    这里先讲一下第(x+1)轮时每一维位移实际上的历史最大值和历史最小值。

    由于(tot_t)已经强制大于等于(0)了,那么历史最小值实际上就是(Mn_t)

    而历史最大值既可能是上一轮的历史最大值,也可能是这一轮新的历史最大值,也就是(max{tot_t imes(x-1)+Mx_t,tot_t imes x+smx_t}),也可以写作(tot_t imes x+max{Mx_t-tot_t,smx_t})

    接着,我们依旧先考虑它是否可能作为一个结束操作。

    第一个必要条件依旧是此时每一维依然都满足未全部越界,即每一维的历史最大值减历史最小值都不超过这一维的坐标范围。

    第二个条件就是满足当前达到的位置之前从未到达过,需要满足(tot_{c_i} ot=0,sum_{c_i}>smx_{c_i},Mx_t-tot_t<smx_{c_i}),应该都是比较好理解的,这里就不多做解释了。

    而此时的答案就是:

    [(nx+i)sum_{t ot=c_i}(w_t-(tot_t imes x+max{Mx_t-tot_t,smx_t}-Mn_t)) ]

    暴拆多项式

    首先在第(1)轮结束的贡献我们是可以直接做的,那么关键就是后面的部分了。

    根据我们推出的答案式,发现其中很多项实际上都是常量,它其实就是(k)个关于(x)的二项式乘在了一起!

    对于每一个操作我们直接暴力多项式乘法把这个多项式拆开,同时发现最多操作轮数(p)也是可以直接枚举统计的。

    因此我们要做的就是求(sum_{x=1}^pf(x))

    考虑把它按不同的幂次分别考虑,其实这就是(k)个自然数幂和!

    那么直接拉格朗日插值与处理一下自然数幂和的系数就可以做了。

    代码:(O(nk^2))

    //考场代码,可能有点丑陋
    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 500000
    #define K 10
    #define X 1000000007
    using namespace std;
    const int f[11][15]=//打完表之后才想起拉格朗日插值的打表代码可以直接放代码里,但打都打完就算了
    {
    	{1,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
    	{0,500000004,500000004,0,0,0,0,0,0,0,0,0,0,0,0},
    	{0,166666668,500000004,333333336,0,0,0,0,0,0,0,0,0,0,0},
    	{0,0,250000002,500000004,250000002,0,0,0,0,0,0,0,0,0,0},
    	{0,766666672,0,333333336,500000004,400000003,0,0,0,0,0,0,0,0,0},
    	{0,0,916666673,0,416666670,500000004,166666668,0,0,0,0,0,0,0,0},
    	{0,23809524,0,833333339,0,500000004,500000004,142857144,0,0,0,0,0,0,0},
    	{0,0,83333334,0,708333338,0,583333338,500000004,125000001,0,0,0,0,0,0},
    	{0,766666672,0,222222224,0,733333338,0,666666672,500000004,111111112,0,0,0,0,0},
    	{0,0,450000003,0,500000004,0,100000000,0,750000006,500000004,700000005,0,0,0,0},
    	{0,348484851,0,500000003,0,1,0,1000000006,0,833333340,500000004,818181824,0,0,0}
    };
    int n,k,c[N+5],d[N+5],w[K+5];
    int tot[K+5],Mx[K+5],Mn[K+5],sum[K+5],smx[K+5],smn[K+5],g[K+5];
    I void Solve()
    {
    	RI i,j,p;for(i=1;i<=n;++i) tot[c[i]]+=d[i],//模拟一轮操作统计tot,Mx,Mn
    		Mx[c[i]]=max(Mx[c[i]],tot[c[i]]),Mn[c[i]]=min(Mn[c[i]],tot[c[i]]);
    	for(i=1;i<=k;++i) if(tot[i]) break;if(i>k)
    		for(i=1;i<=n;++i) if(Mx[i]-Mn[i]<w[i]) puts("-1"),exit(0);//判死循环
    	for(i=1;i<=k;++i) if(tot[i]<0) for(tot[i]*=-1,//强制tot≥0
    		Mx[i]*=-1,Mn[i]*=-1,swap(Mx[i],Mn[i]),j=1;j<=n;++j) if(c[j]==i) d[j]*=-1;
    	RI s,t=0,ans=0,op,o,a,b;for(i=1;i<=n;++i)//枚举每个操作
    	{
    		if((sum[c[i]]+=d[i])>=smn[c[i]]&&sum[c[i]]<=smx[c[i]]) continue;//如果不可能产生贡献就跳过
    		if(sum[c[i]]>smx[c[i]]) ++smx[c[i]],op=1;else --smn[c[i]],op=0;//更新历史最值,op记下是更新哪种最值,后面会用到
    		if(smx[c[i]]-smn[c[i]]==w[c[i]])//如果这一个操作直接走完了
    		{
    			for(p=1,j=1;j<=k;++j) p=1LL*p*w[j]%X;ans=(1LL*(p-t+X)*i+ans)%X;goto Print;//用总点数减去已有点数,求出剩余总答案
    		}
    		for(s=j=1;j<=k;++j) if(j^c[i]) s=1LL*s*(w[j]-(smx[j]-smn[j]))%X;//第1轮结束
    		t=(t+s)%X,ans=(1LL*i*s+ans)%X;//统计答案
    		if(!tot[c[i]]||!op||Mx[c[i]]-tot[c[i]]>=smx[c[i]]) continue;//如果不可能在第x+1轮结束就跳过
    		for(j=1;j<=k;++j) g[j]=0;g[0]=1;//清空多项式
    		for(p=(w[c[i]]-(smx[c[i]]-Mn[c[i]]))/tot[c[i]],j=1;j<=k;++j) if(j^c[i])
    		{
    			a=tot[j],b=max(smx[j],Mx[j]-tot[j])-Mn[j];
    			if(!a) {if(b>=w[j]) {p=0;break;}}else p=min(p,(w[j]-1-b)/a);//p统计最多进行轮数
    			a=(X-a)%X,b=(w[j]-b+X)%X;
    			for(s=k;~s;--s) g[s]=(1LL*b*g[s]+(s?1LL*a*g[s-1]:0))%X;//将这个二项式ax+b乘到总多项式上
    		}
    		if(p<=0) continue;//如果进行轮数小于等于0跳过
    		for(s=1LL*g[0]*p%X,j=1;j<=10;s=(1LL*g[j]*a+s)%X,++j)
    			for(b=1,a=o=0;o<=14;++o) a=(1LL*f[j][o]*b+a)%X,b=1LL*b*p%X;t=(t+s)%X;//统计点数
    		for(s=k;~s;--s) g[s]=(1LL*i*g[s]+(s?1LL*n*g[s-1]:0))%X;//乘上二项式nx+i
    		for(s=1LL*g[0]*p%X,j=1;j<=10;s=(1LL*g[j]*a+s)%X,++j)
    			for(b=1,a=o=0;o<=14;++o) a=(1LL*f[j][o]*b+a)%X,b=1LL*b*p%X;ans=(ans+s)%X;//统计答案
    	}
    	Print:printf("%d
    ",ans);
    }
    int main()
    {
    	freopen("walk.in","r",stdin),freopen("walk.out","w",stdout);
    	RI i;for(scanf("%d%d",&n,&k),i=1;i<=k;++i) scanf("%d",w+i);
    	for(i=1;i<=n;++i) scanf("%d%d",c+i,d+i);return Solve(),0;
    }
    
  • 相关阅读:
    luogu P3375 【模板】KMP字符串匹配
    leetcode[129]Sum Root to Leaf Numbers
    leetcode[130]Surrounded Regions
    leetcode[131]Palindrome Partitioning
    leetcode[132]Palindrome Partitioning II
    leetcode[133]Clone Graph
    leetcode[134]Gas Station
    leetcode[135]Candy
    leetcode[136]Single Number
    leetcode[137]Single Number II
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Luogu7116.html
Copyright © 2020-2023  润新知