码迷,mamicode.com
首页 > 其他好文 > 详细

USACO2020JAN Platinum T2 官方正解的翻译?

时间:2020-01-29 00:44:14      阅读:196      评论:0      收藏:0      [点我收藏+]

标签:break   string   efi   ret   spl   pos   分数   its   pen   

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]\)即可。剩下的询问递归下去处理即可。

<注1>补充一下,具体生成所有合法的\([i,M]?\)\([M+1,i]?\)子序列个数是这样的:

  1. 对于左半区间\([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]\)的累加。

  2. 考虑右半区间时同理,\(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;

USACO2020JAN Platinum T2 官方正解的翻译?

标签:break   string   efi   ret   spl   pos   分数   its   pen   

原文地址:https://www.cnblogs.com/pmt2018/p/12239401.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!