1. 程式人生 > 其它 >noip2018模擬測試賽(三十五)

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\) 能獲得最大的餐票價值為 \(b\left(i,j\right)\) ,走過的路程為 \(a\left(i,j\right)\)

因為我們發現,既然 \(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)\)

,總的時間複雜度即為 \(O(nmlogn)\)

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;
}