USACO2020JAN Platinum T2 官方正解的翻译?
感觉这道题一时半会儿讲不清楚
所以把文章翻译一遍,然后以注解的形式把讲的不太清楚的地方重新理解以下。
(Benjamin Qi的分析)
以下令\(MOD=10^9+7\)。
一些常数优化技巧:
- 将\(MOD\)定义为常量。
- 在加或减两个
int
时避免使用%运算。 - 对于下文中提到的举证,使用固定大小的二维数组储存(而非c++中的vector套vector)。
- 不必遍历矩阵中等于零的项(即后文中矩阵的下对角线部分)。
定义一个关于模运算的独立的类(或struct)也会帮助卡常。
为了方便,我们认为值域范围是\([0,N)\)而非\([1,N]\)。
同时后文会用到前文用到过的变量
Subtask 1:
我们可以在\(O(NK^2)\)的复杂度内计算每一对\((L,R)(1\le L\le R\le N)\)的答案。
具体而言,我们枚举左端点\(L\),然后先让\(R=L\)再不断的递增\(R\)。
用\(tot_i\)表示以数字\(i(i\in[0,K)\ )\)结尾的不降子序列有多少个。(将空子序列看做以零结尾)
然后逐个往后添加数字更新\(tot\)数组。
这样我们就可以\(O(1)\)回答每个查询了。
线段树 (subtasks 2,3):
考虑到往区间\([L,R]\)后添加数字\(x\)更新\(tot\)数组的过程其实是一个矩阵乘法\(tot\times M_x\)的过程。
例如当\(K=5\)时向\(tot\)后添加一个\(3\)的过程相当于:
\[
M3=\begin{bmatrix}1&0&0&1&0\\0&1&0&1&0\\0&0&1&1&0\\0&0&0&2&0\\0&0&0&0&1\end{bmatrix}
\]
满足
\[
[c_0\ \ c_1\ \ c_2\ \ c_3 \ \ c_4]\cdot M_3=[c_0\ \ c_1\ \ c_2\ \ c_0+c_1+c_2+2c_3 \ \ c_4]
\]
换句话说往后添加一个3相当于把\(c_3\)增加了\(c_0+c_1+c_2+c_3\)
这启发我们用线段树来处理区间的矩阵乘法。
若一个节点表示\([L,R]\),那它就应该表示矩阵\(M=M_{A_L}\times\cdots\times M_{A_R}\)
这样我们就可以\(O(NK^3)\)建树,单次查询\(O(K^3\log N)\)的解决掉了。
总复杂度是\(O((N+Q\log N)K^3)\)的,大概率过不掉Subtask2。
我们可以优化建树与查询的过程。
- 对于查询,我们只需要知道矩阵乘积的第一行的结果。所以我们只需要处理\(1\times K\)的矩阵和\(K\times K\)的矩阵相乘,这样单次查询复杂度就变成了\(O(K^2\log N)\),这样就可以过掉subtask2了。
- 对于建树,我们将每\(K\)个矩阵捆绑起来当成一个矩阵(类似分块?)然后对捆绑后的矩阵我们建一棵线段树。然后对于每次查询我们划分成捆绑后的矩阵乘积和零碎的数字。对于零碎的数字我们仿照Subtask1一样暴力\(O(K)\)加入一个数。这样查询的复杂度也不会受影响。这样总复杂度就是\(O(\dfrac{N}{K}K^3+QK^2logN)\)
优化后可以过subtask3,甚至可以拿到全部分数。
分治(满分做法)
线段树做法也可以修改,但是实在没有必要使用线段树。
事实上,给定\(b_1,b_2,\ldots,b_N\)和一个\(O(1)\)的运算\(\oplus\),\(Q\)次查询\(\bigoplus\limits_{i=L}^Rb_i\),我们可以将询问离线下来,均摊\(O(1)\)的回答。
具体而言,我们每次把区间\([L,R]\)划分为\([L,M]\)和\([M+1,R]\),预处理出所有\([i,M]\),\([M+1,i]\)区间的答案,然后对于跨过\(M\)的区间\([l,r]\),我们将\([l,M]\)和\([M+1,r]\)合并即可。对于剩下不跨过\(M\)的区间,我们递归处理即可。
在这道题中,我们可以处理出同时包含\(M\)和\(M+1\)的区间。我们假设子序列的下标为\(j_1<j_2<\ldots<j_a\le M<j_{a+1}<\ldots<j_x\)。我们枚举\(A_{j_a}\)的值,生成所有合法的\([i,M]\)和\([M+1,i]\)区间的答案。这样的复杂度是\(O(NK^2)\)的。对于一个\([l,r]\)的询问,我们用\(O(K)\)的时间合并\([l,M]\)和\([M+1,r]\)即可。剩下的询问递归下去处理即可。
补充一下,具体生成所有合法的\([i,M]?\)和\([M+1,i]?\)子序列个数是这样的:
- 对于左半区间\([L,M]\),\(lans[i][j]\)表示在\([i,M]\)中有多少个以数字\(j\)结尾的不降子序列。
如官方正解中的代码段
void countLeft(int a,int b) { for(int i=a;i<=b;i++) for(int k=1;k<=K;k++) lans[i][k] = 0; for(int k=K;k>=1;k--) { for(int j=k;j<=K;j++) cnt[j] = 0; for(int i=b;i>=a;i--) { if(A[i] == k) { cnt[k] = msum(2*cnt[k] + 1); for(int j=k+1;j<=K;j++) cnt[j] = msum(msum(2*cnt[j]) + lans[i][j]); } for(int j=k;j<=K;j++) lans[i][j] = msum(lans[i][j] + cnt[j]); } } }
我们先枚举\(k\),表示我们当前考虑以\([k,K]\)结尾的不降子序列。然后我们按从\(M\)到\(L\)的顺序枚举当前位置,\(cnt[j]\)表示当前位置到\(M\)中 有多少个子序列 以\(k\)开头且以\(j\)结尾。显然\(lans[i][j]\)就是枚举不同\(k\)时在位置\(i\)时\(cnt[j]\)的累加。
- 考虑右半区间时同理,\(rans[i][j]\)表示\([M+1,i]\)中有多少个以j开头的非降子序列,不过考虑到我们枚举的是\(A_{j_a}\)的值,我们把\(rans[i][j]\)做一个前缀和,让其表示\([M+1,i]\)中有多少个以大于j的数开头的非降子序列。
官方正解
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
#define MAXN 200000
#define MAXQ 200000
#define MOD 1000000007
int msum(int a)
{
if(a >= MOD) return a-MOD;
return a;
}
int N,K,Q;
int A[MAXN];
int l[MAXQ], r[MAXQ];
int qid[MAXQ];
int qans[MAXQ];
int lans[MAXN][21];
int rans[MAXN][21];
int cnt[21];
void countLeft(int a,int b)
{
for(int i=a;i<=b;i++)
for(int k=1;k<=K;k++)
lans[i][k] = 0;
for(int k=K;k>=1;k--)
{
for(int j=k;j<=K;j++)
cnt[j] = 0;
for(int i=b;i>=a;i--)
{
if(A[i] == k)
{
cnt[k] = msum(2*cnt[k] + 1);
for(int j=k+1;j<=K;j++)
cnt[j] = msum(msum(2*cnt[j]) + lans[i][j]);
}
for(int j=k;j<=K;j++)
lans[i][j] = msum(lans[i][j] + cnt[j]);
}
}
}
void countRight(int a,int b)
{
for(int i=a;i<=b;i++)
for(int k=1;k<=K;k++)
rans[i][k] = 0;
for(int k=1;k<=K;k++)
{
for(int j=1;j<=k;j++)
cnt[j] = 0;
for(int i=a;i<=b;i++)
{
if(A[i] == k)
{
cnt[k] = msum(2*cnt[k] + 1);
for(int j=1;j<k;j++)
cnt[j] = msum(msum(2*cnt[j]) + rans[i][j]);
}
for(int j=1;j<=k;j++)
rans[i][j] = msum(rans[i][j] + cnt[j]);
}
}
}
int split(int qa,int qb, int m)
{
int i = qa;
int j = qb;
while(i<j)
{
if(r[qid[i]] > m && r[qid[j]] <= m)
{
swap(qid[i],qid[j]);
i++, j--;
}
else if(r[qid[i]] > m)
j--;
else if(r[qid[j]] <= m)
i++;
else
i++, j--;
}
if(i > j) return j;
else if(r[qid[i]] <= m) return i;
else return i-1;
}
void solve(int a,int b,int qa,int qb)
{
if(a>b || qa>qb) return;
if(a == b)
{
for(int i=qa;i<=qb;i++)
qans[qid[i]] = 1;
return;
}
int m = (a+b)/2;
countLeft(a,m);
countRight(m+1,b);
for(int i=m+1;i<=b;i++)
for(int k=K-1;k>=1;k--)
rans[i][k] = msum(rans[i][k] + rans[i][k+1]);
int qDone = 0;
for(int i=qa;i<=qb;i++)
{
int q = qid[i];
if(r[q] > m && l[q] <= m)
{
qans[q] = 0;
for(int k=1;k<=K;k++)
qans[q] = msum(qans[q] + (lans[l[q]][k]*((long long)rans[r[q]][k]))%MOD);
for(int k=1;k<=K;k++)
qans[q] = msum(qans[q] + lans[l[q]][k]);
qans[q] = msum(qans[q] + rans[r[q]][1]);
qDone++;
}
else if(qDone>0)
qid[i-qDone] = qid[i];
}
qb -= qDone;
int qm = split(qa,qb,m);
solve(a,m,qa,qm);
solve(m+1,b,qm+1,qb);
}
int main()
{
freopen("nondec.in","r",stdin);
freopen("nondec.out","w",stdout);
cin >> N >> K;
for(int i=0;i<N;i++)
cin >> A[i];
cin >> Q;
for(int i=0;i<Q;i++)
{
cin >> l[i] >> r[i];
l[i]--,r[i]--;
qid[i] = i;
}
solve(0,N-1,0,Q-1);
for(int i=0;i<Q;i++)
cout << qans[i]+1 << '\n';
}
我的代码:
#include<bits/stdc++.h>
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define y0 pmt
#define y1 pmtpmt
#define x0 pmtQAQ
#define x1 pmtQwQ
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef vector<int > vi;
typedef pair<int ,int > pii;
const int INF=0x3f3f3f3f, maxn=200007;
const int MOD=1e9+7;
const ll LINF=0x3f3f3f3f3f3f3f3fLL;
const ll P=19260817;
char nc(){
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline ll read(){
ll x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=((x<<3)+(x<<1)+ch-'0')%MOD,ch=getchar();
return x*f;
}
void write(int x){
if(!x)putchar('0');if(x<0)x=-x,putchar('-');
static int sta[20];register int tot=0;
while(x)sta[tot++]=x%10,x/=10;
while(tot)putchar(sta[--tot]+48);
}
int n,K,Q;
struct Query{
int l,r,id;
}q[maxn],q1[maxn],q2[maxn];
// int len1,len2;
int a[maxn];
int cnt[27];
int lans[maxn][27],rans[maxn][27];
int ans[maxn];
int mod1(int a){return a>MOD?a-MOD:a;}
void left(int l,int r){
for(int i=l;i<=r;i++)
for(int j=1;j<=K;j++)lans[i][j]=0;
for(int k=K;k>=1;k--){
for(int j=k;j<=K;j++)cnt[j]=0;
for(int i=r;i>=l;i--){
if(a[i]==k){
cnt[k]=mod1(mod1(cnt[k]*2)+1);
for(int j=k+1;j<=K;j++)cnt[j]=mod1(mod1(cnt[j]*2)+lans[i][j]);
}
for(int j=k;j<=K;j++)lans[i][j]=mod1(lans[i][j]+cnt[j]);
}
}
}
void right(int l,int r){
for(int i=l;i<=r;i++)
for(int j=1;j<=K;j++)rans[i][j]=0;
for(int k=1;k<=K;k++){
for(int j=1;j<=k;j++)cnt[j]=0;
for(int i=l;i<=r;i++){
if(a[i]==k){
cnt[k]=mod1(mod1(cnt[k]<<1)+1);
for(int j=1;j<k;j++)cnt[j]=mod1(mod1(cnt[j]<<1)+rans[i][j]);
}
for(int j=1;j<=k;j++)rans[i][j]=mod1(rans[i][j]+cnt[j]);
}
}
for(int i=l;i<=r;i++)
for(int k=K-1;k>=1;k--)
rans[i][k]=mod1(rans[i][k]+rans[i][k+1]);
}
void solve(int l,int r,int ql,int qr){
if(l>r||ql>qr)return ;
if(l==r){
for(int i=ql;i<=qr;i++){
ans[q[i].id]=1;
}
return ;
}
int mid=(l+r)>>1;
left(l,mid);right(mid+1,r);
int len1=0,len2=0;
for(int i=ql;i<=qr;i++){
if(q[i].r<=mid)q1[++len1]=q[i];
else if(q[i].l>mid)q2[++len2]=q[i];
else {
for(int k=1;k<=K;k++)ans[q[i].id]=mod1(ans[q[i].id]+1ll*lans[q[i].l][k]*rans[q[i].r][k]%MOD);
for(int k=1;k<=K;k++)ans[q[i].id]=mod1(ans[q[i].id]+lans[q[i].l][k]);
ans[q[i].id]=mod1(ans[q[i].id]+rans[q[i].r][1]);
}
}
for(int i=ql;i<ql+len1;i++)q[i]=q1[i-ql+1];
for(int i=ql+len1;i<ql+len1+len2;i++)q[i]=q2[i-ql-len1+1];
solve(l,mid,ql,ql+len1-1);
solve(mid+1,r,ql+len1,ql+len1+len2-1);
}
int main(){
scanf("%d%d",&n,&K);
for(int i=1;i<=n;i++)scanf("%d",a+i);
scanf("%d",&Q);
for(int i=1;i<=Q;i++)
scanf("%d%d",&q[i].l,&q[i].r),q[i].id=i;
solve(1,n,1,Q);
for(int i=1;i<=Q;i++)printf("%d\n",mod1(ans[i]+1));
return 0;
}
复杂度是\(O(NK^2\log N+Q(K+\log N))\)的
矩阵逆(满分做法)
令\(ipref[x]=M_{A_{x-1}}^{-1}\cdot M_{A_{x-2}}^{-1}\cdots M_{A_1}^{-1}\),以及\(pref[x]=M_{A_1}\cdot M_{A_2}\cdots M_{A_{x-1}}\),计算\([M_x^{-1}]\)其实相当容易。
例如当\(K=5\)时,
\[
M_3^{-1}=\begin{bmatrix}
1 & 0 & 0 & -1/2 & 0 \0 & 1 & 0 & -1/2 & 0 \0 & 0 & 1 & -1/2 & 0 \0 & 0 & 0 & 1/2 & 0 \0 & 0 & 0 & 0 & 1 \\end{bmatrix},
\]
满足
\[
\begin{bmatrix}
c_0 & c_1 & c_2 & c_0+c_1+c_2+2c_3 & c_4
\end{bmatrix}\cdot M_3^{-1}=
\begin{bmatrix}
c_0 & c_1 & c_2 & c_3 & c_4
\end{bmatrix}.
\]
从而又因为\(M_x\)以及\(M_x^{-1}\)和单位矩阵很像,我们可以用乘法分配率在\(O(NK^2)\)的时间内处理出\(pref,ipref\)。
那么\([L,R]\)的答案\(A_L,\ldots,A_R\)的不降子序列个数其实就是\(\sum\limits_{i=0}^{K-1}(ipref[L-1]\cdot pref[R])[0][i]\),单次复杂度是\(O(K^2)\)的。
考虑到我们只需要乘积矩阵的第一行,答案可以改写为\(\sum\limits_{i=0}^{K-1}(ipref[L-1][0]\cdot pref[R])[i]\\ =\sum_{i=0}^{K-1}(ipref[L-1][0][i]\cdot \left(\sum_{j=0}^{K-1}pref[R][i][j]\right)).\)
\((\sum_{j=0}^{K-1}pref[R][i][j])\)显然可以预处理,然后就没了。
复杂度是\(O(NK^2+QK)\)的。
官方sol:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD = 1e9+7; // 998244353; // = (119<<23)+1
const int MX = 5e4+5;
void setIO(string name) {
ios_base::sync_with_stdio(0); cin.tie(0);
freopen((name+".in").c_str(),"r",stdin);
freopen((name+".out").c_str(),"w",stdout);
}
struct mi {
int v; explicit operator int() const { return v; }
mi(ll _v) : v(_v%MOD) { v += (v<0)*MOD; }
mi() : mi(0) {}
};
mi operator+(mi a, mi b) { return mi(a.v+b.v); }
mi operator-(mi a, mi b) { return mi(a.v-b.v); }
mi operator*(mi a, mi b) { return mi((ll)a.v*b.v); }
typedef array<array<mi,20>,20> T;
int N,K,Q;
vector<int> A;
array<mi,20> sto[MX], isto[MX];
mi i2 = (MOD+1)/2;
void prin(T& t) { // print a matrix for debug purposes
for (int i = 0; i < K; ++i) {
for (int j = 0; j < K; ++j)
cout << t[i][j].v << ' ';
cout << "\n";
}
cout << "-------\n";
}
int main() {
setIO("nondec");
cin >> N >> K; A.resize(N);
for (int i = 0; i < N; ++i) cin >> A[i];
T STO, ISTO;
for (int i = 0; i < K; ++i)
STO[i][i] = ISTO[i][i] = 1;
for (int i = 0; i <= N; ++i) {
for (int j = 0; j < K; ++j)
for (int k = j; k < K; ++k)
sto[i][j] = sto[i][j]+STO[j][k];
for (int k = 0; k < K; ++k)
isto[i][k] = ISTO[0][k];
if (i == N) break;
int x = A[i]-1;
// STO goes from pre[i] to pre[i+1]
// set STO = STO*M_{A[i]}
for (int j = 0; j <= x; ++j)
for (int k = x; k >= j; --k)
STO[j][x] = STO[j][x]+STO[j][k];
// ISTO goes from ipre[i] to ipre[i+1]
// set ISTO=M_{A[i]}^{-1}*ISTO
for (int j = 0; j < x; ++j)
for (int k = x; k < K; ++k)
ISTO[j][k] = ISTO[j][k]-i2*ISTO[x][k];
for (int k = x; k < K; ++k)
ISTO[x][k] = ISTO[x][k]*i2;
}
cin >> Q;
for (int i = 0; i < Q; ++i) {
int L,R; cin >> L >> R;
mi ans = 0;
for (int j = 0; j < K; ++j)
ans = ans+isto[L-1][j]*sto[R][j];
cout << ans.v << "\n";
}
}
我就不写了/kk;
原文地址:https://www.cnblogs.com/pmt2018/p/12239401.html