Yakiniku Restaurants
题目思路
容易发现走回头路一定不会更优,因此答案走过的路程一定为一段区间
考虑 (O(n^2)) 暴力枚举区间,再 (O(m)) 计算每种餐票能获得的最大值,时间复杂度为 (O(n^2m)) ,卡不过
考虑dp优化,假设对于餐厅 (i) 来说,从餐厅 (j) 走到餐厅 (i) 能获得最大的价值,那么称 (j) 为 (i) 的最优决策点
我们发现,这个dp是具有决策单调性的,也就是说若 (j) 为 (i) 的最优决策点,那么 (i+1) 的最优决策点一定在 (j) 之后
假设餐厅 (j) 走到餐厅 (i) 能获得最大的餐票价值为 (bleft(i,j ight)) ,走过的路程为 (aleft(i,j ight))
因为我们发现,既然 (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
",l[y]);
rt=merge(x,merge(y,z));
}
}
return 0;
}