B. Once Again... 解析(思維、DP、LIS、矩陣冪)
Codeforce 582 B. Once Again... 解析(思維、DP、LIS、矩陣冪)
今天我們來看看CF582B
題目連結
題目
給你一個長度為\(n\)的數列\(a\),求\(a\)循環\(T\)次以後的最大遞增子序列(LIS)。\(n\le100,T\le10^7\)
前言
這題實在是搞了非常非常久,經驗過於不足,無論是矩陣快速冪的奇怪\(dp\)式或者是偏思維作法,都搞不太出來

想法
首先,要來講一下\(LIS\)的作法:
- \(dp:\) 我們維護以第\(i\)個元素為結尾的\(LIS\)長度為\(dp[i]\),轉移式\(:dp[i]=\max\limits_{j=0,a[j]\le a[i]}^n\{dp[i],dp[j]+1\}\) 且要考慮\(LIS\)只有單一個元素時長度為1,所以\(dp[i]\)有個最小值\(1\)。
複雜度:\(O(n^2)\) - 二分搜\(:\) 我們維護一個\(stack\),且每次加入一個元素考慮\(LIS\)。\(stack\)頂端放目前維護的\(LIS\)的最後一個元素,如果新元素\(\ge\)頂端的元素,那就加入。如果新元素\(<\)頂端的元素,那麼我們可以二分搜目前維護的\(LIS\)中第一個大於新元素的元素,並且替換為新元素,如此一來,因為我們換了一個比較小的元素上去,且不影響目前的\(LIS\)長度,因此整個\(stack\)在之後能獲得更好的遞增子序列的「潛力」就變高了。非常建議直接看code思考為什麼這樣做
複雜度:\(O(n\lg(n))\) 
int LIS(){
  int sta[_n*_n],top=0;
  sta[top++]=a[0];
  rep(i,1,nn){
    if(a[i]>=sta[top-1])sta[top++]=a[i];
    else *upper_bound(sta,sta+top,a[i])=a[i];
  }
  return top;
}
回歸正題,這題有兩種做法,偏思維作法或者是奇怪\(dp\)式做法。
偏思維作法
畢竟循環(\(T\))太長了,而\(n\)又很小,所以自然地感覺應該會用到經典的\(LIS\)(最大遞增子序列)問題再加上一些觀察。
注意到,最慢最慢在數列\(a\)循環\(n\)次以後,\(LIS\)會重複某個字元。也就是如果每次循環我們都取一個相異的元素,那麼\(n\)次循環以後就不可能再取到相異的元素了。而因此,我們會想要去維護長度為\(n^2\)的\(LIS\)(如果\(T>n\))。
現在,如果\(T\le n\),那麼我們只要暴力算出\(LIS\)就行,畢竟\(n\)很小。
如果\(T>n\),觀察到,因為\(T>n\),所以我們可以想像得出,整串\(LIS\)一定有非常多重複的數字,整串\(LIS\)會先遞增,然後到某個數字開始重複,接著在尾端繼續遞增。
那麼我們只要先算出數列\(a\)中最多重複出現幾次同樣的數字,並且算出長度\(n^2\)的\(LIS\)以後,由於我們知道當\(T>n\)時,至少會有\(T-n\)次\(a\)數列的循環會貢獻給\(LIS\)同樣的數字,因此,解答就是:\(LIS(長度n^2的)+(T-n)\times(出現次數最多的數字)\)。(想法來源)
還有另一個可能會想到的作法,但是複雜度會高一些,直接放連結奇怪\(dp\)式做法:
其實這個做法是矩陣快速冪,但我不知道為什麼想得到這種狀態
考慮\(dp\)狀態: \(dp[i][j]\)表示「目前為止考慮過的循環節」(考慮過幾個循環節並沒有在狀態中)的,開頭元素\(\ge a[i]\)且結尾為最後一個循環的第\(j\)個元素的\(LIS\)長度。
而會有一個轉移式\(:dp_{p+q}[i][j]=\max\limits_{k=0}^n\{dp_p[i][k]+dp_q[k][j]\}\)(就是把兩個對於(不/相)同長度循環節的\(LIS\)接起來)
其中\(dp_p\)表示目前這個\(dp\)狀態考慮的數列長度是\(p\times n\)(也就是\(p\)個循環節)
我們想要求的是\(dp_{nT}\),而我們可以用樸素的\(dp\)算法(\(O(n^2)\)的方法)來算出\(dp_1\)。
把\(dp[][]\)看成是矩陣,而一次轉移看成是一次矩陣乘法,那麼就可以用矩陣快速冪算出來了。
要注意如果在算\(dp[i][j]\)時如果\(a[i]>a[j]\)代表這種可能不可能發生,要給一個極小值。
複雜度:偏思維作法中較慢的\(:O(n^4)\)、偏思維作法中較快的想法但是沒用二分搜\(LIS:O(n^4)\)、矩陣快速冪\(:O(n^3\lg(T))\)、偏思維作法中較快的想法且有用二分搜\(:O(n^2\lg(n^2))\)
程式碼(偏思維作法中較慢的):
const int _n=110;
int tt,a[_n*_n],st[_n*_n],ed[_n*_n],num[310];
ll n,nn,t;
main(void) {ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
  cin>>n>>t;rep(i,0,n){cin>>a[i];num[a[i]]++;} nn=min(n*n,n*t);
  rep(i,n,nn)a[i]=a[i%n];
  rep(i,0,nn){ed[i]=1;{rep(j,0,i)if(a[j]<=a[i])ed[i]=max(ed[i],ed[j]+1);}}
  per(i,0,nn){st[i]=1;{per(j,i+1,nn)if(a[j]>=a[i])st[i]=max(st[i],st[j]+1);}}
  int maxx=-1;
  if(t<n)rep(i,0,nn)maxx=max(maxx,ed[i]);
  else rep(i,0,nn)maxx=max(maxx,ed[i]+st[i]-1+num[a[i]]*(t-n));
  cout<<maxx<<'\n';
  return 0;
}
標頭、模板請點Submission看
Submission
程式碼(偏思維作法中較快的想法但是沒用二分搜):
const int _n=110;
int tt,a[_n*_n],dp[_n*_n],num[310];
ll n,nn,t,k;
main(void) {ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
  cin>>n>>t;rep(i,0,n){cin>>a[i];num[a[i]]++;k=max(k,num[a[i]]);}
  nn=min(n*n,n*t);
  rep(i,n,nn)a[i]=a[i%n]; dp[0]=1;
  rep(i,1,nn){
    dp[i]=1;
    rep(j,0,i)if(a[j]<=a[i])dp[i]=max(dp[i],dp[j]+1);
  }
  if(t>n){
    int ans=k*(t-n);
    int maxx=0;rep(i,0,nn)maxx=max(maxx,dp[i]);
    ans+=maxx;
    cout<<ans<<'\n';
  }else{
    int maxx=0;rep(i,0,nn)maxx=max(maxx,dp[i]);
    cout<<maxx<<'\n';
  }
  return 0;
}
標頭、模板請點Submission看
Submission
程式碼(矩陣快速冪):
const int _n=110;
int t,n,a[_n];
struct mat{
  int a[_n][_n];
  mat(){memset(a,0,sizeof a);}
  mat operator*(const mat& rhs)const{
    mat res;
    rep(i,0,n)rep(j,0,n){
      res.a[i][j]=-1e5;
      rep(k,0,n)res.a[i][j]=max(res.a[i][j],a[i][k]+rhs.a[k][j]);
    }
    return res;
  }
  mat operator^(int b){
    mat res,tmp=*this;
    while(b){
      if(b&1)res=res*tmp;
      b>>=1;tmp=tmp*tmp;
    }
    return res;
  }
};
main(void) {ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
  cin>>n>>t;rep(i,0,n)cin>>a[i]; mat ans;
  rep(i,0,n)rep(j,0,n){
    if(a[i]>a[j]){ans.a[i][j]=-1e5;continue;}
    ans.a[i][j]=1;
    rep(k,0,j)if(a[k]<=a[j])ans.a[i][j]=max(ans.a[i][j],ans.a[i][k]+1);
  }ans=ans^t;
  int maxx=0;rep(i,0,n)rep(j,0,n)maxx=max(maxx,ans.a[i][j]);
  cout<<maxx<<'\n';
  return 0;
}
標頭、模板請點Submission看
Submission
程式碼(偏思維作法中較快的想法且有用二分搜):
const int _n=110;
int tt,a[_n*_n],num[310];
ll n,nn,t,k;
int LIS(){
  int sta[_n*_n],top=0;
  sta[top++]=a[0];
  rep(i,1,nn){
    if(a[i]>=sta[top-1])sta[top++]=a[i];
    else *upper_bound(sta,sta+top,a[i])=a[i];
  }
  return top;
}
main(void) {ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
  cin>>n>>t;rep(i,0,n){cin>>a[i];num[a[i]]++;k=max(k,num[a[i]]);}
  nn=min(n*n,n*t);
  rep(i,n,nn)a[i]=a[i%n];
  if(t>n){
    int ans=k*(t-n);
    ans+=LIS();
    cout<<ans<<'\n';
  }else cout<<LIS()<<'\n';
  return 0;
}
標頭、模板請點Submission看
Submission
B. Once Again... 解析(思維、DP、LIS、矩陣冪)的更多相关文章
- B. Kay and Snowflake 解析(思維、DFS、DP、重心)
		
Codeforce 685 B. Kay and Snowflake 解析(思維.DFS.DP.重心) 今天我們來看看CF685B 題目連結 題目 給你一棵樹,要求你求出每棵子樹的重心. 前言 完全不 ...
 - D. Alyona and Strings 解析(思維、DP)
		
Codeforce 682 D. Alyona and Strings 解析(思維.DP) 今天我們來看看CF682D 題目連結 題目 略,請直接看原題. 前言 a @copyright petjel ...
 - C. Vladik and Memorable Trip 解析(思維、DP)
		
Codeforce 811 C. Vladik and Memorable Trip 解析(思維.DP) 今天我們來看看CF811C 題目連結 題目 給你一個數列,一個區段的數列的值是區段內所有相異數 ...
 - D. New Year Santa Network 解析(思維、DFS、組合、樹狀DP)
		
Codeforce 500 D. New Year Santa Network 解析(思維.DFS.組合.樹狀DP) 今天我們來看看CF500D 題目連結 題目 給你一棵有邊權的樹,求現在隨機取\(3 ...
 - B. Nauuo and Circle 解析(思維、DP)
		
Codeforce 1172 B. Nauuo and Circle 解析(思維.DP) 今天我們來看看CF1172B 題目連結 題目 略,請直接看原題 前言 第一個該觀察的事情一直想不到,看了解答也 ...
 - D. Maximum Distributed Tree 解析(思維、DFS、組合、貪心、DP)
		
Codeforce 1401 D. Maximum Distributed Tree 解析(思維.DFS.組合.貪心.DP) 今天我們來看看CF1401D 題目連結 題目 直接看原題比較清楚,略. 前 ...
 - A. Arena of Greed 解析(思維)
		
Codeforce 1425 A. Arena of Greed 解析(思維) 今天我們來看看CF1425A 題目連結 題目 略,請直接看原題. 前言 明明是難度1400的題目,但總感覺不是很好寫阿, ...
 - E. Almost Regular Bracket Sequence 解析(思維)
		
Codeforce 1095 E. Almost Regular Bracket Sequence 解析(思維) 今天我們來看看CF1095E 題目連結 題目 給你一個括號序列,求有幾個字元改括號方向 ...
 - C2. Power Transmission (Hard Edition) 解析(思維、幾何)
		
Codeforce 1163 C2. Power Transmission (Hard Edition) 解析(思維.幾何) 今天我們來看看CF1163C2 題目連結 題目 給一堆點,每兩個點會造成一 ...
 
随机推荐
- 对之前IoT项目的完善
			
博文有点长,因为是两个大项目(四个小项目)放一起了,不过都很适合新手小白(有源程序的情况),也可以再接 OLED 屏,就是前几篇博客的操作 一.esp8266 读取 DHT11 数据并通过微信小程序发 ...
 - 《SpringCloudDubbo开发日记》(一)Nacos连官方文档都没写好
			
背景 现在的微服务框架一般分dubbo和springcloud两套服务治理体系,dubbo是基于zookeeper为注册中心,springcloud是基于eureka作为注册中心. 但是现在eurek ...
 - dhtmlxGantt独立安装的系统要求
			
dhtmlxGantt库提供了使用导出作为在线服务从甘特图导出和导入数据的可能性. 您还可以通过在计算机上安装导出服务来本地导出甘特图.您需要确保系统满足系统要求才能使用导出模块: PNG / PDF ...
 - Layman 使用ffmpeg-php扩展库实现视频截图(默认图)
			
这几天做项目,其中一个需求是用户上传视频文件到服务器,然后服务器自动截取该视频的一帧作为该视频对应的缩略图,服务器端语言采用php编写,找了半天资料,发现ffmpeg-php可以满足该需求,所以下面简 ...
 - matlab中repmat函数的用法
			
转载:https://blog.csdn.net/facetosea1/article/details/83573859 B = repmat(A,m,n)B = repmat(A,[m n])B = ...
 - 【题解】SP1811 LCS - Longest Common Substring
			
\(\color{purple}{Link}\) \(\text{Solution:}\) 题目要求找到两个串的最长公共子串.\(LCP\) 我们将两个串中间和末尾插入终止符,并弄到一棵后缀树上去. ...
 - Centos7安装MySQL8.0(RPM方式)
			
人生处处皆学问,工作也是如此!过去不止一次在Linux上安装MySQL,可以说轻车熟路,但是写篇文章总结一下,发现有很多细节值得学习! 安装包选择 为什么用rpm? 在Linux系列上安装软件一般有源 ...
 - 扫描仪扫描文件处理-富士通ix500参数
			
纸张太薄不要扫,非常容易卡纸 当纸张薄的时候,每次不要放入太多,很容易因为层叠纸张压力导致滚动拉动单张力度过大,从而卡纸 卡纸第一时间叩开"滚轮盖" 去掉"自动跳过空白页 ...
 - redis集群搭建 不用ruby
			
redis 从5开始 可以直接用redis-cli命令创建集群了,不用那么麻烦 安装ruby环境 redis配置文件需要修改的地方 port 7000 cluster-enabled yes clus ...
 - selenium 提取天猫网页数据
			
from time import sleep from selenium import webdriver br = webdriver.Chrome() url = "https://ww ...