题目链接:acm.hdu.edu.cn/showproblem.php?pid=6589

题意:给出一个长度为n的数组,有m次操作,操作有3种1,2,3,问操作m次后的数组,输出i*a[i]的异或和

操作k的实质是进行一次O(n)的计算,a[i]+=a[i-k] (i-k>0)

k=1时,我们可以发现这是一次求前缀和的操作

k=2时,我们可以发现这是对于1,3,5,7... 2,4,6,8...两个子数组分别进行求前缀和的操作

k=3时,我们可以发现这是对于1,4,7,11...2,5,8,12...3,6,9,12...三个子数组分别求前缀和的操作

暴力的复杂度是O(mn),我们可以模拟出暴力的过程,其实这并不是一个浪费时间的过程,因为在比赛时,我们通过这个暴力的程序验算样例,发现了一个性质,那就是操作顺序的改变,并不会影响结果!

这个性质是解题的关键,如果没有发现这个性质,那么是想不到正解的,那么,问题的本质就变成了如何快速求出m次前缀和,粗略一想很显然这还是个o(nm)的操作,其实不然

观察求前缀和的过程

0次(不求):a[1],a[2],a[3],a[4],a[5]...

1次:         a[1],a[2]+a[1],a[3]+a[2]+a[1],a[4]+a[3]+a[2]+a[1],a[5]+a[4]+a[3]+a[2]+a[1]...

2次:         a[1],a[2]+2a[1],a[3]+2a[2]+3a[1],a[4]+2a[3]+3a[2]+4a[1],a[5]+2a[4]+3a[3]+4a[2]+5a[1]...

3次:         a[1],a[2]+3a[1],a[3]+3a[2]+6a[1],a[4]+3a[3]+6a[2]+10a[1],a[5]+3a[4]+6a[3]+10a[2]+15a[1]...

...

这里,规律就很明显了,我们可以发现进行多次前缀和后的数组,它的结果是和组合数有关的

第m次,组合数数组应该是c[i]=C(m+i-2,i-1),那么,上述结果用数组表示就是

m次:      c[1]*a[1],c[1]*a[2]+c[2]*a[1],c[1]*a[3]+c[2]*a[2]+c[3]*a[1],c[1]*a[4]+c[2]*a[3]+c[3]*a[2]+c[4]*a[1],c[1]*a[5]+c[2]*a[4]+c[3]*a[3]+c[4]*a[2]+c[5]*a[1]...

这个东西已经很明显了,就是数组a[1],a[2],a[3],a[4],a[5]... 与b[1],b[2],b[3],b[4],b[5]...求卷积的结果,组合数的求法,O(m)预处理,O(1)求解即可,这是个很经典的方法,这里就不再赘述,百度上很多

求卷积有NTT(快速数论变换)与FFT(快速傅立叶变换)两种方法,也许你并不会这两个方法,这没有关系,套模板就行了,对于k=2,k=3的情况,我们只需要将数组拆分成子数组,就可以变成k=1的形式了,问题也就解决了

值得一提的是,由于FFT是复数操作,存在浮点误差,而且取模是一个魔法操作(不会),所以这里还是用NTT比较合适,注意一个细节,由于要多次使用板子,所以每次用完一定要把板子里面应该重置的数据要初始化,

特别是那两个用来求卷积的数组!!!

做一次卷积,我们就可以得到n次前缀和后的数组,整体时间复杂度O(m+nlogn)

上代码:

#include <bits/stdc++.h>
using namespace std;
#define maxn 300005//注意用来求卷积的数组的大小
#define MOD 998244353
#define mod MOD
#define G 3
typedef long long ll;
namespace NTT {//模板内容
int rev[maxn], n, m;
long long A[maxn], B[maxn], C[maxn]; inline ll Pow(ll a, ll k) {
ll base = 1;
while (k) {
if (k & 1) base = (base * a) % MOD;
a = (a * a) % MOD;
k >>= 1;
}
return base % MOD;
} void NTT(long long *a, int len, int opt) {
for (int i = 0; i < len; i++) {
if (i < rev[i]) {
swap(a[i], a[rev[i]]);
}
}
for (int i = 1; i < len; i <<= 1) {
long long wn = Pow(G, (opt * ((MOD - 1) / (i << 1)) + MOD - 1) % (MOD - 1));
int step = i << 1;
for (int j = 0; j < len; j += step) {
long long w = 1;
for (int k = 0; k < i; k++, w = (1ll * w * wn) % MOD) {
long long x = a[j + k];
long long y = 1ll * w * a[j + k + i] % MOD;
a[j + k] = (x + y) % MOD;
a[j + k + i] = (x - y + MOD) % MOD;
}
}
}
if (opt == -1) {
long long r = Pow(len, MOD - 2);
for (int i = 0; i < len; i++)
a[i] = 1ll * a[i] * r % MOD;
}
} void solve(int n, int m) {
int x, l = 0 ,len = 1;
while (len <= n + m) len <<= 1, ++l;
for (int i = 0; i < len; ++i)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (l - 1));
NTT(A, len, 1), NTT(B, len, 1);
for (int i = 0; i < len; ++i){
C[i] = (ll) (A[i] * B[i]) % MOD;
A[i]=B[i]=0;
}
NTT(C, len, -1);
}
}
template <class T>
void read(T &x) {
static char ch;static bool neg;
for(ch=neg=0;ch<'0' || '9'<ch;neg|=ch=='-',ch=getchar());
for(x=0;'0'<=ch && ch<='9';(x*=10)+=ch-'0',ch=getchar());
x=neg?-x:x;
}
int n,cnt[4];
ll a[100005],c[100005];
ll fac[1000005],inv[1000005];
ll pow_mod(ll m,ll n)
{
ll res=1;
while (n)
{
if(n&1)res=res*m%mod;
m=m*m%mod;
n>>=1;
}
return res;
}
void init()
{
inv[0]=fac[0]=1;
for(int i=1;i<=1000000;i++)fac[i]=fac[i-1]*i%mod;
inv[1000000]=pow_mod(fac[1000000],mod-2);
for(int i=999999;i>=1;i--)inv[i]=inv[i+1]*(i+1)%mod;
}
ll C(int n,int m)
{
if(m==0)return 1;//这个地方是为了特殊处理调cnt=0的情况,这个时候的c数组应该是1,0,0,0,0...
if(n-m<0)return 0;
return fac[n]*inv[n-m]%mod*inv[m]%mod;
}
void calc(int m)//求m次前缀和的组合数数组
{
for(int i=1;i<=n;i++){
c[i]=C(m-2+i,i-1);
}
}
int main()
{
init();//组合数预处理
int T;
cin>>T;
while (T--)
{
memset(cnt,0, sizeof(cnt));
int m,op;
read(n);read(m);
for(int i=1;i<=n;i++)
{
read(a[i]);
}
for(int i=1;i<=m;i++)
{
read(op);
++cnt[op];
}
calc(cnt[1]);
for(int i=0;i<n;i++)NTT::A[i]=a[i+1];
for(int i=0;i<n;i++)NTT::B[i]=c[i+1];
NTT::solve(n,n);
for(int i=0;i<n;i++)a[i+1]=NTT::C[i];
calc(cnt[2]);
vector<int>d1,d2,d3;
for(int i=1;i<=n;i++)
{
i%2?d1.emplace_back(a[i]):d2.emplace_back(a[i]);
}
for(int i=0;i<d1.size();i++)NTT::A[i]=d1[i];
for(int i=0;i<d1.size();i++)NTT::B[i]=c[i+1];
NTT::solve(d1.size(),d1.size());
for(int i=1;i<=n;i+=2)a[i]=NTT::C[i/2];
for(int i=0;i<d2.size();i++)NTT::A[i]=d2[i];
for(int i=0;i<d2.size();i++)NTT::B[i]=c[i+1];
NTT::solve(d2.size(),d2.size());
for(int i=2;i<=n;i+=2)a[i]=NTT::C[i/2-1];
d1.clear();
d2.clear();
calc(cnt[3]);
for(int i=1;i<=n;i++)
{
if(i%3==1)d1.emplace_back(a[i]);
else if(i%3==2)d2.emplace_back(a[i]);
else d3.emplace_back(a[i]);
}
for(int i=0;i<d1.size();i++)NTT::A[i]=d1[i];
for(int i=0;i<d1.size();i++)NTT::B[i]=c[i+1];
NTT::solve(d1.size(),d1.size());
for(int i=1;i<=n;i+=3)a[i]=NTT::C[i/3];
for(int i=0;i<d2.size();i++)NTT::A[i]=d2[i];
for(int i=0;i<d2.size();i++)NTT::B[i]=c[i+1];
NTT::solve(d2.size(),d2.size());
for(int i=2;i<=n;i+=3)a[i]=NTT::C[i/3];
for(int i=0;i<d3.size();i++)NTT::A[i]=d3[i];
for(int i=0;i<d3.size();i++)NTT::B[i]=c[i+1];
NTT::solve(d3.size(),d3.size());
for(int i=3;i<=n;i+=3)a[i]=NTT::C[i/3-1];
ll ans=0;
for(int i=1;i<=n;i++)ans=ans^(1ll*i*a[i]);
cout<<ans<<endl;
}
return 0;
}

标程给了一个更好的思路,k=2时,我们把k=1的那种c数组变成c[1],0,c[2],0,c[3],0....

k=3时,变成c[1],0,0,c[2],0,0,c[3],0,0...这种,然后直接对两个数组求卷积就可以了

这是按照标程思路写的代码,精简了很多,常数也小了一些

#include <bits/stdc++.h>
using namespace std;
#define maxn 300005
#define MOD 998244353
#define mod MOD
#define G 3
typedef long long ll;
int rev[maxn];
long long C[maxn]; inline ll Pow(ll a, ll k) {
ll base = ;
while (k) {
if (k & ) base = (base * a) % MOD;
a = (a * a) % MOD;
k >>= ;
}
return base % MOD;
} void NTT(long long *a, int len, int opt) {
for (int i = ; i < len; ++i) {
if (i < rev[i]) {
swap(a[i], a[rev[i]]);
}
}
for (int i = ; i < len; i <<= ) {
long long wn = Pow(G, (opt * ((MOD - ) / (i << )) + MOD - ) % (MOD - ));
int step = i << ;
for (int j = ; j < len; j += step) {
long long w = ;
for (int k = ; k < i; ++k, w = (1ll * w * wn) % MOD) {
long long x = a[j + k];
long long y = 1ll * w * a[j + k + i] % MOD;
a[j + k] = (x + y) % MOD;
a[j + k + i] = (x - y + MOD) % MOD;
}
}
}
if (opt == -) {
long long r = Pow(len, MOD - );
for (int i = ; i < len; i++)
a[i] = 1ll * a[i] * r % MOD;
}
} void solve(ll A[],ll B[],int n, int m) {
int x, l = ,len = ;
while (len <= n + m) len <<= , ++l;
for (int i = ; i < len; ++i)
rev[i] = (rev[i >> ] >> ) | ((i & ) << (l - ));
NTT(A, len, ), NTT(B, len, );
for (int i = ; i < len; ++i) {
C[i] = (ll) (A[i] * B[i]) % MOD;
A[i] = B[i] = ;
}
NTT(C, len, -);
}
void read(ll &x) {
static char ch;static bool neg;
for(ch=neg=;ch<'' || ''<ch;neg|=ch=='-',ch=getchar());
for(x=;''<=ch && ch<='';(x*=)+=ch-'',ch=getchar());
x=neg?-x:x;
}
int n,cnt[];
ll a[maxn],c[maxn];
ll fac[],inv[];
ll pow_mod(ll m,ll n)
{
ll res=;
while (n)
{
if(n&)res=res*m%mod;
m=m*m%mod;
n>>=;
}
return res;
}
void init()
{
inv[]=fac[]=;
for(int i=;i<=;i++)fac[i]=fac[i-]*i%mod;
inv[]=pow_mod(fac[],mod-);
for(int i=;i>=;i--)inv[i]=inv[i+]*(i+)%mod;
}
ll Comb(int n,int m)
{
return n<m?:fac[n]*inv[n-m]%mod*inv[m]%mod;
}
int main()
{
init();//组合数预处理
int T;
cin>>T;
while (T--)
{
memset(cnt,, sizeof(cnt));
ll m,op;
cin>>n>>m;
for(int i=;i<=n;i++)
{
read(a[i]);
}
for(int i=;i<=m;i++)
{
read(op);
++cnt[op];
}
for(int i=;i<=;i++)
{
memset(c,, sizeof(c));
for(int j=;j*i<n;j++)
{
c[j*i]=Comb(cnt[i]-+j,j);
}
if(cnt[i]==)c[]=;//特殊处理
solve(a+,c,n,n);
for(int i=;i<n;i++)a[i+]=C[i];
}
ll ans=;
for(int i=;i<=n;i++)ans=ans^(1ll*i*a[i]);
cout<<ans<<endl;
}
return ;
}

总结:这个题总体来说还是不难的,虽然过程繁琐,比赛的时候用了FFT也没写出来,不过总的收获还是很大的,以前对于这种比赛时过的很少的题束手无策,现在也能自己分析个七七八八的,算是一种进步了吧

多想想,不要轻易放弃,也许下一刻就能收获AC!

HDU多校训练第一场 1012 Sequence的更多相关文章

  1. 牛客网多校训练第一场 I - Substring(后缀数组 + 重复处理)

    链接: https://www.nowcoder.com/acm/contest/139/I 题意: 给出一个n(1≤n≤5e4)个字符的字符串s(si ∈ {a,b,c}),求最多可以从n*(n+1 ...

  2. HDU多校练习第一场4608——I_Number

    题目:点击打开链接 水题一道,刚开始写了一发模拟,后来发现所谓的10^5是个length……果断加了个大数枚举,过了,今天换了个样式重写了个. 易于推出,两个数之间的最大差值为20. #include ...

  3. 牛客网多校训练第一场 J - Different Integers(树状数组 + 问题转换)

    链接: https://www.nowcoder.com/acm/contest/139/J 题意: 给出n个整数的序列a(1≤ai≤n)和q个询问(1≤n,q≤1e5),每个询问包含两个整数L和R( ...

  4. 牛客网多校训练第一场 F - Sum of Maximum(容斥原理 + 拉格朗日插值法)

    链接: https://www.nowcoder.com/acm/contest/139/F 题意: 分析: 转载自:http://tokitsukaze.live/2018/07/19/2018ni ...

  5. 牛客网多校训练第一场 E - Removal(线性DP + 重复处理)

    链接: https://www.nowcoder.com/acm/contest/139/E 题意: 给出一个n(1≤n≤1e5)个整数(范围是1至10)的序列,求从中移除m(1≤m≤min(n-1, ...

  6. 牛客网多校训练第一场 D - Two Graphs

    链接: https://www.nowcoder.com/acm/contest/139/D 题意: 两个无向简单图都有n(1≤n≤8)个顶点,图G1有m1条边,图G2有m2条边,问G2有多少个子图与 ...

  7. 牛客网多校训练第一场 B - Symmetric Matrix(dp)

    链接: https://www.nowcoder.com/acm/contest/139/B 题意: 求满足以下条件的n*n矩阵A的数量模m:A(i,j) ∈ {0,1,2}, 1≤i,j≤n.A(i ...

  8. 牛客网多校训练第一场 A - Monotonic Matrix(Lindström–Gessel–Viennot lemma)

    链接: https://www.nowcoder.com/acm/contest/139/A 题意: 求满足以下条件的n*m矩阵A的数量模(1e9+7):A(i,j) ∈ {0,1,2}, 1≤i≤n ...

  9. 19暑假多校训练第一场-J-Fraction Comparision(大数运算)

    链接:https://ac.nowcoder.com/acm/contest/881/J来源:牛客网 题目描述 Bobo has two fractions xaxa and ybyb. He wan ...

随机推荐

  1. Address already in use : connect

    Address already in use : connect 错误以及处理 项目中有过手写并发测试,在长时间的并发测试(超过20秒,美妙超过2000)的情况下出现了以上错误 处理方法如下(抄的) ...

  2. 20-基于 DSP TMS320C6455的6U CPCI高速信号处理板卡

    基于 DSP TMS320C6455的6U CPCI高速信号处理板卡 1. 板卡概述 基于 DSP TMS320C6455的CPCI高速信号处理板卡是新一代高速DSP处理平台,广泛用于DSP性能验证, ...

  3. React(4) --引入图片及循环数据

    引入图片的方法 1.引入本地图片 方法1: import logo from '../assets/images/1.jpg'; <img src={logo} /> 方法2: <i ...

  4. 嵌入式系统的性能测试(1) – lmbench篇

    要评价一个系统的性能,通常有不同的指标,相应的会有不同的测试方法和测试工具.既有比较成熟的商业测试软件,也有许多优秀的开源工具来完成这个任务.本文简要介绍如何使用lmbench来完成系统综合性能测试. ...

  5. hadoop集群常见问题解决

    1:namenode启动 datanode未启动 解决: /hadoop/tmp/dfs/name/current VERSION 查看截取id 与 data/current VERSION集群ID ...

  6. bzoj5210 最大连通子块和 动态 DP + 堆

    题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=5210 题解 令 \(dp[x][0]\) 表示以 \(x\) 为根的子树中的包含 \(x\) ...

  7. [BZOJ1023][SHOI2008]cactus仙人掌图 DP

    题目链接 套路就是先考虑一般的树上做法.求直径的dp的做法大家应该都会吧. 那么设\(dp[i]\)表示\(i\)的子树中的点到\(i\)的最大距离. 在dp的过程中 \[ ans=\max\{dp[ ...

  8. Linux中检查内存使用情况的命令

    Linux操作系统包含大量工具,所有这些工具都可以帮助您管理系统.从简单的文件和目录工具到非常复杂的安全命令,在Linux上没有太多不能做的事情.而且,虽然普通桌面用户可能不需要在命令行熟悉这些工具, ...

  9. php sqrt()函数 语法

    php sqrt()函数 语法 作用:sqrt()函数的作用是对参数进行求平方根 语法:sqrt(X) 参数: 参数 描述 X 进行求平方根的数字 说明:返回将参数X进行开平方后的结果江苏大理石平台 ...

  10. POJ 3525 Most Distant Point from the Sea (半平面交)

    Description The main land of Japan called Honshu is an island surrounded by the sea. In such an isla ...