题目链接: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. 攻防世界--re2-cpp-is-awesome

    测试文件:https://adworld.xctf.org.cn/media/task/attachments/c5802869b8a24033b4a80783a67c858b 1.准备 获取信息 6 ...

  2. windows下nvm的安装及使用

    由于更新了npm版本之后导致npm的命令都会报错,一顿百度,明白了nvm可以管理node版本的,下面是操作过程: 如果在安装nvm之前已经下载了node 需要把node卸载!!! 需要把node卸载! ...

  3. Ubuntu18.04+CUDA9.0+cuDNN7.1.3+TensorFlow1.8 安装总结

    Ubuntu18.04发行已经有一段时间了,正好最近Tensorflow也发布了1.8版本,于是决定两个一起装上,以下是安装总结,大致可 以分为5个步骤 确认当前软件和硬件环境.版本 更新显卡驱动,软 ...

  4. XMPP即时通讯协议使用(七)——利用Strophe实现WebIM及strophe.plugins插件使用

    Strophe简介与Openfire配置 Strophe.js是为XMPP写的一个js类库.因为http协议本身不能实现持久连接,所以strophe利用BOSH模拟实现持久连接. 官方文档: http ...

  5. GeneXus笔记本—常用函数(下)

    这篇是常用函数的最后一节 当然 我这里聊的还不是全部的,需要各位朋友继续在工作中去深入才行啊 ,毕竟从入门到入土....┌(; ̄◇ ̄)┘ 1:Sleep 这个函数你们应该能猜到 ”To allow m ...

  6. 微信小程序(15)--上传图片公用组件(2)

    接下来开始写写上传图片的公用组件,可以自定义上传几张图片. chooseImage文件夹里面的index.wxml和index.js,涉及图片上传,删除,预览. <view class=&quo ...

  7. 分布式理论 BASE、CAP、ACID

    CAP原理: 在理论计算机科学中,CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer's theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点: 一致性(Co ...

  8. html5 自制播放器

    代码实例: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...

  9. pspice介绍1(转载)

    PSpice的主要功能及特点: OrCAD软件的主要组成包括:OrCAD/Capture CIS.OrCAD/Layout Plus.OrCAD/Express及OrCAD/PSpice.它们分别是: ...

  10. kd树解平面最近点对

    早上起来头有点疼,突然就想到能不能用kd树解平面最近点对问题,就找了道题试了一下,结果可以,虽然效率不高,但还是AC了~ 题目链接:http://acm.hdu.edu.cn/showproblem. ...