在牛子老师的博客下边看到 yspm 给了 CF1019E。看了一眼,不会。看了题解,我超边分治 + 闵可夫斯基和,一个都不会。乐。
还有 20 天,还能补多少坑呢,不好说。
仍然是每天高压作业。但是出乎意料的晚上不是很失眠,虽然说醒了以后还是很困。
现象:让大象出现的事物或者方法。大象是一种体量很大、包容万物的生物,平时喜欢躲在泥土下面拿长长的鼻子冒充仙人掌以攫食长颈鹿。因为大象的鼻子看起来像是罕见的仙人掌,很多人希望能将新奇的仙人掌品种给引入园艺界。此时大象就会拔地而起,狠揍植物猎人并把植物猎人挂在树梢上当做大雁发表演说用的讲台。
同理可定义抽象:一种让大象出现的方法。即用真空泵将大象从地下抽出。
P6932 [ICPC2017 WF] Money for Nothing
洛谷题面翻译什么寄吧。题意是每一天都能倒卖一次,但是自始至终只能和一对交易。也就是左下角卖家右上角买家的矩形最大面积。
首先可能成为答案的点肯定是从左上到右下一排。然后固定左下的点,右上的点的选取有单调性,于是可以按着决策单调性的分治写法直接冲。
忘了怎么写了,贺了一发。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>
#define int long long
using namespace std;
int n,m,ans,cnta,cntb;
struct node{
int x,y;
bool operator<(const node&s)const{
return x==s.x?y<s.y:x<s.x;
}
}a[500010],b[500010],tmp[500010];
void solve(int l,int r,int L,int R){
if(l>r||L>R)return;
int mid=(l+r)>>1,pos=0,val=-0x3f3f3f3f3f3f3f3f;
for(int i=L;i<=R;i++){
if(a[mid].x<b[i].x||a[mid].y<b[i].y){
int ret=(b[i].x-a[mid].x)*(b[i].y-a[mid].y);
if(ret>val)val=ret,pos=i;
}
}
ans=max(ans,val);
if(pos){
solve(l,mid-1,L,pos);solve(mid+1,r,pos,R);
}
}
signed main(){
scanf("%lld%lld",&m,&n);
for(int i=1;i<=m;i++)scanf("%lld%lld",&tmp[i].x,&tmp[i].y);
sort(tmp+1,tmp+m+1);a[0].y=0x3f3f3f3f3f3f3f3f;
for(int i=1;i<=m;i++){
if(a[cnta].y>tmp[i].y)a[++cnta]=tmp[i];
}
for(int i=1;i<=n;i++)scanf("%lld%lld",&tmp[i].x,&tmp[i].y);
sort(tmp+1,tmp+n+1);
for(int i=1;i<=n;i++){
while(cntb&&b[cntb].y<tmp[i].y)cntb--;
b[++cntb]=tmp[i];
}
solve(1,cnta,1,cntb);
printf("%lld\n",ans);
return 0;
}
gym103202L Forged in the Barrens
十分奇妙。
首先看上去就有个最大流建模。模拟最大流大概也能过。但我们不这么干,因为有点丑陋。
每个区间一头的贡献是 \(+1\),一头是 \(-1\)。那么考虑一个 dp:设 \(dp_{0/1/2,0/1/2,l,r,x}\) 为区间 \([l,r]\) 选了 \(x\) 段,左右没有东西 / 空了一段正贡献 / 负贡献。则转移考虑分治,有:
\[dp_{s_l,s_r,l,r,x}=\max(\max_{x_1+x_2=x}dp_{s_l,0,l,mid,x_1}+dp_{0,s_r,mid+1,r,x_2},\\ \max_{x_1+x_2+1=x}dp_{s_l,1,l,mid,x_1}+dp_{2,s_r,mid+1,r,x_2},\\ \max_{x_1+x_2+1=x}dp_{s_l,2,l,mid,x_1}+dp_{1,s_r,mid+1,r,x_2},\\ dp_{s_l,s_r,l,mid,x},dp_{s_l,s_r,mid+1,r,x})\]发现这玩意关于 \(x\) 是凸的,于是直接闵可夫斯基和合并凸包。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
#define int long long
int n,a[200010];
const int inf=0x3f3f3f3f3f3f3f3f;
struct node{
vector<int>v[3][3];
};
vector<int>Merge(vector<int>a,vector<int>b){
for(int i=a.size()-1;i>=1;i--)a[i]-=a[i-1];
for(int i=b.size()-1;i>=1;i--)b[i]-=b[i-1];
vector<int>c(a.size()+b.size()-1);
c[0]=a[0]+b[0];
merge(a.begin()+1,a.end(),b.begin()+1,b.end(),c.begin()+1,greater<int>());
for(int i=1;i<a.size()+b.size()-1;i++)c[i]+=c[i-1];
return c;
}
vector<int>max(vector<int>a,vector<int>b){
int n=max(a.size(),b.size());
while(a.size()<n)a.push_back(-inf);
while(b.size()<n)b.push_back(-inf);
for(int i=0;i<n;i++)a[i]=max(a[i],b[i]);
return a;
}
node solve(int l,int r){
if(l==r){
node ans;
ans.v[0][0].push_back(0);ans.v[0][0].push_back(0);
ans.v[1][0].push_back(-inf);ans.v[1][0].push_back(a[l]);
ans.v[2][0].push_back(-a[l]);
ans.v[0][1].push_back(-inf);ans.v[0][1].push_back(a[l]);
ans.v[1][1].push_back(-inf);
ans.v[2][1].push_back(-inf);
ans.v[0][2].push_back(-a[l]);
ans.v[1][2].push_back(-inf);
ans.v[2][2].push_back(-inf);
return ans;
}
int mid=(l+r)>>1;
node L=solve(l,mid),R=solve(mid+1,r),ans;
for(int l=0;l<3;l++){
for(int r=0;r<3;r++){
ans.v[l][r]=max(max(Merge(L.v[l][0],R.v[0][r]),Merge(L.v[l][1],R.v[2][r])),max(Merge(L.v[l][2],R.v[1][r]),max(L.v[l][r],R.v[l][r])));
}
}
return ans;
}
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
node ans=solve(1,n);
for(int i=1;i<=n;i++)printf("%lld\n",ans.v[0][0][i]);
return 0;
}
uoj#592. 新年的聚会
比较神奇。
考虑随机打乱所有点,然后维护若干个独立集。每次找一个 \(x\),对每个独立集分治找到和它们连的所有边,然后加入最小的独立集里边。复杂度不会证明。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <random>
#include <ctime>
#include <vector>
#include "meeting.h"
using namespace std;
int n,m,cnt,id[1010];
vector<pair<int,int> >ans;
vector<int>v[2010];
mt19937 rnd(time(0)^(unsigned long long)(new char));
bool check(int pos,int x,int l,int r){
vector<int>ret;ret.push_back(x);
for(int i=l;i<=r;i++)ret.push_back(v[pos][i]);
return meeting(ret);
}
bool solve(int pos,int x,int l,int r,bool jud){
if(l>r)return false;
if(!jud&&check(pos,x,l,r))return false;
if(l==r){
ans.push_back(make_pair(x,v[pos][l]));
return true;
}
int mid=(l+r)>>1;
if(!check(pos,x,l,mid)){
solve(pos,x,l,mid,true);solve(pos,x,mid+1,r,false);
}
else solve(pos,x,mid+1,r,true);
return true;
}
vector<pair<int,int> >solve(int n){
for(int i=0;i<n;i++)id[i]=i;
shuffle(id,id+n,rnd);
for(int i=0;i<n;i++){
int x=id[i],pos=-1;
for(int j=1;j<=cnt;j++){
if(!solve(j,x,0,v[j].size()-1,false)){
if(pos==-1||v[j].size()<v[pos].size())pos=j;
}
}
if(pos==-1)pos=++cnt;
v[pos].push_back(x);
}
return ans;
}
CF1442D Sum
大结论题。
结论是过程中最多只有一个序列被选了一部分。
证明:反证,假设有两个序列 \(a,b\) 选到了 \(a_x,b_y\),其中 \(a_x\ge b_y\),那么选到 \(a_{x+1},b_{y-1}\) 一定更优。
于是就相当于做一个强制某个元素不选的背包,直接分治就好了。
轻微卡常(也可能是我实现有问题),每个分治节点开个 dp 会 T。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
#define int long long
using namespace std;
int n,k,ans,sum[3010],cnt[3010],dp[3010][21];
vector<int>a[3010];
void solve(int l,int r,int dep){
if(l==r){
int sum=0,cnt=0;
ans=max(ans,dp[k][dep]);
for(int x:a[l]){
cnt++;sum+=x;
if(cnt>k)break;
ans=max(ans,dp[k-cnt][dep]+sum);
}
return;
}
int mid=(l+r)>>1;
for(int i=0;i<=k;i++)dp[i][dep+1]=dp[i][dep];
for(int i=mid+1;i<=r;i++){
for(int j=k;j>=cnt[i];j--)dp[j][dep+1]=max(dp[j][dep+1],dp[j-cnt[i]][dep+1]+sum[i]);
}
solve(l,mid,dep+1);
for(int i=0;i<=k;i++)dp[i][dep+1]=dp[i][dep];
for(int i=l;i<=mid;i++){
for(int j=k;j>=cnt[i];j--)dp[j][dep+1]=max(dp[j][dep+1],dp[j-cnt[i]][dep+1]+sum[i]);
}
solve(mid+1,r,dep+1);
}
signed main(){
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++){
scanf("%lld",&cnt[i]);
for(int j=1;j<=cnt[i];j++){
int x;scanf("%lld",&x);a[i].push_back(x);sum[i]+=x;
}
}
for(int i=1;i<=k;i++)dp[i][0]=-0x3f3f3f3f3f3f3f3f;
solve(1,n,0);
printf("%lld\n",ans);
return 0;
}
CF1553H XOR and Distance
参考 x义x 老师的神秘解法。
设 \(dp_{x,d}\) 为强制 \(x\) 使用和 \(x\) 相比只有低 \(d\) 位不同的元素的答案,\(mn{x,d}\) 为它们和 \(x\) 异或最小值,\(mx_{x,d}\) 为最大值。
考虑转移到 \(d+1\)。\(mn,mx\) 的转移比较显然:设 \(x,y\) 满足 \(y=x+2^d\),则
\[mn_{x,d+1}=\min(mn_{x,d},mn_{y,d}+2^d) \]\(mx\) 的式子是一样的。那么考虑转移 \(dp\)。元素对有三类:
- 都在 \((x,d)\) 中:就是 \(dp_{x,d}\)。
- 都在 \((y,d)\) 中:就是 \(dp_{y,d}\)。
- 两个都在:这个可以发现是 \(mn_{x,d}-mx_{y,d}+2^d\)。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
int n,k,dp[1<<19],mx[1<<19],mn[1<<19];
int main(){
scanf("%d%d",&n,&k);
memset(dp,0x3f,sizeof(dp));memset(mn,0x3f,sizeof(mn));memset(mx,-0x3f,sizeof(mx));
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
mx[x]=mn[x]=0;
}
for(int i=0;i<k;i++){
for(int s=0;s<(1<<k);s++){
if(!((s>>i)&1))continue;
dp[s^(1<<i)]=dp[s]=min(dp[s^(1<<i)],dp[s]);
dp[s^(1<<i)]=min(dp[s^(1<<i)],mn[s]-mx[s^(1<<i)]+(1<<i));
dp[s]=min(dp[s],mn[s^(1<<i)]-mx[s]+(1<<i));
int mn1=mn[s^(1<<i)],mn2=mn[s],mx1=mx[s^(1<<i)],mx2=mx[s];
mn[s^(1<<i)]=min(mn[s^(1<<i)],mn2+(1<<i));
mn[s]=min(mn[s],mn1+(1<<i));
mx[s^(1<<i)]=max(mx[s^(1<<i)],mx2+(1<<i));
mx[s]=max(mx[s],mx1+(1<<i));
}
}
for(int i=0;i<(1<<k);i++)printf("%d ",dp[i]);puts("");
return 0;
}
CF1179E Alesya and Discrete Math
这分治题单是没有贪心是吧。
首先都分治题单了肯定要分治是不是。于是考虑分治:找到每个函数取值为 \(\dfrac L2\) 的位置,然后排序,找到中间的一个,分到左右两半为子问题。询问次数 \(n\log n\log L\),过不去。
分治不好去掉,但是似乎没必要询问每个的值,只要找到最中间的一个函数是谁就行了。随机一个函数检查,然后每次在多的那一半里边重复该过程,那么期望 \(O(\log n)\) 次找到。这样单层的询问次数就从 \(O(n\log L)\) 变成了 \(\sum\log n\log L\),总复杂度是 \(\sum_{i=1}^{\log n}i2^{\log n-i}\log L=O(n\log L)\),可以通过。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#define int long long
using namespace std;
int n,L;
pair<int,int>ans[1010];
int query(int id,int x){
printf("? %lld %lld\n",id,x);
fflush(stdout);
scanf("%lld",&x);
return x;
}
int getpos(int l,int r,int val,int id){
while(l<r){
int mid=(l+r)>>1;
if(query(id,mid)>=val)r=mid;
else l=mid+1;
}
return l;
}
int find(int l,int r,int val,int cnt,vector<int>v){
int pos=v[rand()%v.size()];
int p=getpos(l,r,val,pos);
vector<int>L,R,ret;ret.push_back(pos);
for(int x:v){
if(x==pos)continue;
int tmp=query(x,p);
if(tmp<val)R.push_back(x);
if(tmp>val)L.push_back(x);
if(tmp==val)ret.push_back(x);
}
while(!ret.empty()&&L.size()<cnt)L.push_back(ret.back()),ret.pop_back();
while(!ret.empty())R.push_back(ret.back()),ret.pop_back();
if(L.size()==cnt)return p;
if(L.size()>cnt)return find(l,r,val,cnt,L);
else return find(l,r,val,cnt-L.size(),R);
}
void solve(int l,int r,int L,int R,vector<int> v){
if(v.empty())return;
if(v.size()==1){
ans[v[0]].first=L,ans[v[0]].second=R;
return;
}
int mid=l+(r-l)/v.size()*(v.size()>>1);
int pos=find(L,R,mid,v.size()>>1,v);
vector<int>ll,rr,ret;
for(int x:v){
int tmp=query(x,pos);
if(tmp>mid)ll.push_back(x);
if(tmp<mid)rr.push_back(x);
if(tmp==mid)ret.push_back(x);
}
while(!ret.empty()&&ll.size()<(v.size()>>1))ll.push_back(ret.back()),ret.pop_back();
while(!ret.empty())rr.push_back(ret.back()),ret.pop_back();
solve(l,mid,L,pos,ll);
solve(mid,r,pos,R,rr);
}
signed main(){
scanf("%lld%lld",&n,&L);
vector<int>v;
for(int i=1;i<=n;i++)v.push_back(i);
solve(0,L,0,1e18,v);
puts("!");
for(int i=1;i<=n;i++)printf("%lld %lld\n",ans[i].first,ans[i].second);
return 0;
}
CF865F Egg Roulette
官方题解没看懂。
首先有个神秘结论:对于任意包含不超过 \(R-1\) 个 \(A\) 或 \(B\) 的前缀可以随意重排而不改变不公平度。官方题解说因为该赢的不会输该输的不会赢。感觉很感性。称这样的前缀是合法的。
然后既然这样了就有个比较显然的东西:任意序列都可以提 \(R-1\) 个 \(A\) 和 \(R-1\) 个 \(B\) 放开头而不改变不公平度。
考虑先算出来不公平度,这一点通过不断往最后边塞操作,直到塞到一个合法前缀来得到。首先两个人的操作数是相等的,然后若现在 Alice 有 \(cnta\) 个操作,Bob 有 \(cntb\) 个操作,则若 Alice 往最后边放一个,Bob 增加的胜场数是 Alice 往最后放一个 R 的方案,即 \(\dbinom{cnta-1}{R-1}\dbinom{cntb}R\)。
这部分的复杂度是 \(2^{2C}\) 的,但是可以加个剪枝:先找到一个足够小的不公平度,然后通过最优化剪枝减小状态数。这个可以通过哪边小补哪里的贪心方式填出一个不公平度。
然后是统计答案。同样往最后边塞操作,当塞到一个合法前缀的时候统计答案。假设这时有 \(cnta\) 个 \(A\),\(cntb\) 个 \(B\),而这个前缀有 \(a\) 个确定的 \(A\),\(b\) 个确定的 \(B\),那方案数显然是 \(\dbinom{cnta-a+cntb-b}{cnta-a}\)。记搜即可通过。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <map>
#include <vector>
#define int long long
using namespace std;
int n,m,ans,C[70][70],suma[70],sumb[70];
char s[70];
void dfs(int x,int y,int cnta,int cntb){
if(ans<=1||abs(x-y)-C[cnta][n]*C[cntb][n]>ans)return;
if(cnta<n||cntb<n){
ans=min(ans,abs(x-y));return;
}
if(cnta>=n)dfs(x,y+C[cnta-1][n-1]*C[cntb][n],cnta-1,cntb);
if(cntb>=n)dfs(x+C[cntb-1][n-1]*C[cnta][n],y,cnta,cntb-1);
}
map<int,int>mp[70][70];
int solve(int x,int y,int cnta,int cntb){
if(mp[cnta][cntb].find(x-y)!=mp[cnta][cntb].end())return mp[cnta][cntb][x-y];
if(abs(x-y)-C[cnta][n]*C[cntb][n]>ans)return mp[cnta][cntb][x-y]=0;
if(cnta<n||cntb<n){
if(cnta>=suma[cnta+cntb]&&cntb>=sumb[cnta+cntb])return mp[cnta][cntb][x-y]=C[cnta+cntb-suma[cnta+cntb]-sumb[cnta+cntb]][cnta-suma[cnta+cntb]];
else return mp[cnta][cntb][x-y]=0;
}
int ans=0;
if(s[cnta+cntb]!='B')ans+=solve(x,y+C[cnta-1][n-1]*C[cntb][n],cnta-1,cntb);
if(s[cnta+cntb]!='A')ans+=solve(x+C[cntb-1][n-1]*C[cnta][n],y,cnta,cntb-1);
return mp[cnta][cntb][x-y]=ans;
}
signed main(){
scanf("%lld%lld%s",&n,&m,s+1);
for(int i=0;i<=(n+m<<1);i++){
C[i][0]=1;
for(int j=1;j<=i;j++)C[i][j]=C[i-1][j-1]+C[i-1][j];
}
int x=0,y=0,cnta=n+m,cntb=n+m;
while(cnta>=n&&cntb>=n){
if(x>y)y+=C[cnta-1][n-1]*C[cntb][n],cnta--;
else x+=C[cntb-1][n-1]*C[cnta][n],cntb--;
}
ans=abs(x-y);
dfs(0,0,n+m,n+m);
for(int i=1;i<=(n+m<<1);i++){
suma[i]=suma[i-1]+(s[i]=='A');
sumb[i]=sumb[i-1]+(s[i]=='B');
}
printf("%lld\n",solve(0,0,n+m,n+m));
return 0;
}
标签:专题,cntb,cnta,int,分治,mid,include,dp
From: https://www.cnblogs.com/gtm1514/p/17521480.html