noip2018模擬測試賽(三十五)
Yakiniku Restaurants
題目思路
容易發現走回頭路一定不會更優,因此答案走過的路程一定為一段區間
考慮 \(O(n^2)\) 暴力列舉區間,再 \(O(m)\) 計算每種餐票能獲得的最大值,時間複雜度為 \(O(n^2m)\) ,卡不過
考慮dp優化,假設對於餐廳 \(i\) 來說,從餐廳 \(j\) 走到餐廳 \(i\) 能獲得最大的價值,那麼稱 \(j\) 為 \(i\) 的最優決策點
我們發現,這個dp是具有決策單調性的,也就是說若 \(j\) 為 \(i\) 的最優決策點,那麼 \(i+1\) 的最優決策點一定在 \(j\) 之後
假設餐廳 \(j\)
因為我們發現,既然 \(i\) 的最優決策點不為 \(j\) 之前的店鋪,說明對於 \(i\) 來說,假設 \(k<j\) ,那 \(b(i,k)-b(i,j)\) 一定小於 \(a(i,k)-a(i,j)=a(j,k)\) ,即 \(j\) 之前的店鋪餐票帶來的價值上升一定比不過路程帶來的價值下降;那麼對於 \(i+1\) 來說 \(b(i+1,k)-b(i+1,j)\le b(i,k)-b(i,j)\le a(j,k)\le a(i+1,k)-a(i+1,j)\)
設 \(calc(i,j)\) 為區間 \([i,j]\) 的最大價值,我們直接st表預處理一下對於每種餐票各個區間的最大值即可 \(O(m)\) 求 \(calc\)
因為求解 \(calc\) 不需要之前決策點的資訊,因此我們分治處理。對於區間 \([l,r]\) ,我們對於點 \(mid\) 求出最優決策點 \(p\) ,那麼區間 \([l,mid-1]\) 的最優決策點顯然在 \(p\) 之前,區間 \([mid+1,r]\) 的最優決策點顯然在 \(p\) 之後,這樣的時間複雜度是 \(O(nlogn)\) 的,再加上求解 \(calc\) 的 \(O(m)\)
PS: 似乎還有另一種方法,將區間 \([l,r]\) 轉化為二維上的一個點 \((l,r)\) ,列舉每種餐票,通過單調棧求解左右側首個最大值,通過差分的技巧插入二維平面,然後就可以 \(O(1)\) 查詢 \(calc\) 了,預處理時間複雜度 \(O(nm)\),詢問 \(O(n^2)\)
程式碼
#include<iostream>
using namespace std;
int n,m,dp[5005];
long long a[5005],b[5005][305];
long long lg2[5005]= {-1},h[305][5005][15];
void st(int t) {
for(int i=1; i<=n; i++) {
h[t][i][0]=b[i][t];
}
for(int j=0; j<lg2[n]; j++) {
int mx=n-(1<<(j+1))+1;
for(int i=1; i<=mx; i++) {
h[t][i][j+1]=max(h[t][i][j],h[t][i+(1<<j)][j]);
}
}
}
long long getmax(int t,int l,int r) {
int k=lg2[r-l+1];
return max(h[t][l][k],h[t][r-(1<<k)+1][k]);
}
long long calc(int l,int r) {
if(l>r)return 0;
long long ans=a[l]-a[r];
for(int i=1; i<=m; i++) {
ans+=getmax(i,l,r);
}
return ans;
}
long long ans;
void solve(int l,int r,int x,int y) {
if(x>y)return;
int mid=(x+y)/2;
long long mx=0;
int now=l;
for(int i=l; i<=r&&i<=mid; i++) {
long long c=calc(i,mid);
if(c>=mx)mx=c,now=i;
}
ans=max(ans,mx);
solve(l,now,x,mid-1);
solve(now,r,mid+1,y);
}
int main() {
scanf("%d%d",&n,&m);
for(int i=2; i<=n; i++) {
scanf("%lld",&a[i]);
a[i]+=a[i-1];
}
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++) {
scanf("%lld",&b[i][j]);
}
}
for(int i=1; i<=n; i++)lg2[i]=lg2[i/2]+1;
for(int i=1; i<=m; i++)st(i);
solve(1,n,1,n);
printf("%lld",ans);
return 0;
}
Decrementing
題目思路
如果沒有除最大公約數的操作,那麼直接判斷 \(a_i-1\) 的和的奇偶性即可
因此我們思考除最大公約數的操作會對 \(a_i-1\) 的和的奇偶性產生什麼影響
我們發現,當最大公約數為奇數時操作對 \(a_i-1\) 的和的奇偶性無影響,只有當其是偶數時才有可能產生影響
而所有數的最大公約數為偶數當且僅當所有數都為偶數,因此只有當前局面中只有一個非一奇數時奇偶性才有可能變化
並且如果當前情況對先手而言是必勝的話,他永遠可以通過破壞偶數的方式使得不可能出現最大公約數為偶數,維持必勝局面
那麼當沒有可能變化時直接判斷,有可能變化時我們模擬即可,由於每次至少除二,因此最多隻會模擬 \(O(nlogn)\) 次,總時間複雜度為 \(O(nlogn)\)
程式碼
#include<iostream>
#include<algorithm>
using namespace std;
int n,a[100005];
int jishu() {
int shu=0;
for(int i=1; i<=n; i++) {
if(a[i]==1)return 0;
shu+=(a[i]&1);
}
return shu;
}
void jian() {
for(int i=1; i<=n; i++) {
if(a[i]&1)a[i]--;
}
}
void chu() {
int gongyinshu=a[1];
for(int i=2; i<=n; i++) {
gongyinshu=__gcd(gongyinshu,a[i]);
}
for(int i=1; i<=n; i++) {
a[i]/=gongyinshu;
}
}
int main() {
scanf("%d",&n);
for(int i=1; i<=n; i++) {
scanf("%d",&a[i]);
}
for(int wanjia=1; 1; wanjia^=1) {
long long he=0;
for(int i=1; i<=n; i++) {
he+=a[i]-1;
}
if(he&1) {
if(wanjia)printf("First");
else printf("Second");
break;
} else {
if(jishu()==1) {
jian(),chu();
} else {
if(!wanjia)printf("First");
else printf("Second");
break;
}
}
}
return 0;
}
wangxz與OJ
題目思路
資料結構題
由於題目沒有保證插入的數量與刪除的數量,因此不能像維護序列那樣直接插入,刪除
發現題目插入的都是一段區間,因此我們平衡樹上的一個節點維護的就是一個區間
對於序列題,採用無旋treap,另外split時有可能將一個區間劈成兩個,需要將原來的區間分成兩個區間
注意一下陣列大小即可
程式碼
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<vector>
using namespace std;
const int N=1000005;
int cnt,rt,ls[N],rs[N],rd[N];
int l[N],r[N],size[N];
void pushup(int p) {
size[p]=size[ls[p]]+size[rs[p]]+r[p]-l[p]+1;
}
int add(int a,int b) {
size[++cnt]=b-a+1;
rd[cnt]=rand();
l[cnt]=a,r[cnt]=b;
return cnt;
}
void split(int p,int &a,int &b,int k) {
if(p==0) {
a=b=0;
return;
}
if(k<=size[ls[p]]) {
b=p;
split(ls[p],a,ls[b],k);
} else {
if(k<size[ls[p]]+(r[p]-l[p]+1)) {
int p2=add(l[p]+k-size[ls[p]],r[p]);
r[p]=l[p]+k-size[ls[p]]-1;
rs[p2]=rs[p],rs[p]=p2;
}
a=p;
split(rs[p],rs[a],b,k-size[ls[p]]-(r[p]-l[p]+1));
}
pushup(p);
}
int merge(int a,int b) {
if(a==0||b==0) {
return a|b;
}
if(rd[a]<rd[b]) {
rs[a]=merge(rs[a],b);
pushup(a);
return a;
} else {
ls[b]=merge(a,ls[b]);
pushup(b);
return b;
}
}
void print(int p) {
if(p==0)return;
print(ls[p]);
for(int i=l[p]; i<=r[p]; i++) {
printf("%d ",i);
}
print(rs[p]);
}
int main() {
int n,m;
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++) {
int x;
scanf("%d",&x);
rt=merge(rt,add(x,x));
}
while(m--) {
int op,p,a,b;
scanf("%d",&op);
int x,y,z;
if(op==0) {
scanf("%d%d%d",&p,&a,&b);
split(rt,x,y,p);
rt=merge(x,merge(add(a,b),y));
}
if(op==1) {
scanf("%d%d",&a,&b);
split(rt,y,z,b);
split(y,x,y,a-1);
rt=merge(x,z);
}
if(op==2) {
scanf("%d",&p);
split(rt,y,z,p);
split(y,x,y,p-1);
printf("%d\n",l[y]);
rt=merge(x,merge(y,z));
}
}
return 0;
}