$T1$(Loj2758):
给你一个环和N个切口以及每个切口的位置$A_i$,你需要切三刀将环分成三份使得最小的一块最大。
$Nleq10^5,A_ileq10^9$。
$Solution$:
“最小的一块最大”满足单调性,考虑二分答案然后$check(ans)$。
因为是一个环,我们无法线性$check$,只能枚举第一刀的位置,然后后面每一刀尽量切在最小的$ans$的位置。
在有序表中查找最小的大于等于某数的位置可以直接$lowerbound$,复杂度$O(N imes logN imes logN)$。
$Code$:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> using namespace std; #define MAXN 100005 #define MAXM 500005 #define INF 0x7fffffff #define ll long long ll A[MAXN<<1],sum[MAXN<<1]; ll N,L,R,ans; inline ll read(){ ll x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline bool check(ll x){ for(ll i=1;i<=N;i++){ ll tp=sum[i-1]; ll c1=lower_bound(sum+i,sum+i+N-1,x+tp)-sum; if(!c1 || c1>i+N-1 || sum[c1]<x+tp) continue;tp=sum[c1]; ll c2=lower_bound(sum+i,sum+i+N-1,x+tp)-sum; if(!c2 || c2>i+N-1 || sum[c2]<x+tp) continue;tp=sum[c2]; ll c3=lower_bound(sum+i,sum+i+N-1,x+tp)-sum; if(!c3 || c3>i+N-1 || sum[c3]<x+tp) continue; return 1; } return 0; } int main(){ N=read(),L=0,R=0,ans=0; for(ll i=1;i<=N;i++) A[i]=read(),A[i+N]=A[i]; for(ll i=1;i<=(N<<1);i++) sum[i]=sum[i-1]+A[i]; R=sum[N]; while(L<=R){ ll mid=(L+R)>>1; if(check(mid)) ans=mid,L=mid+1; else R=mid-1; } printf("%lld ",ans); return 0; } /* 61 30 62 1 34 44 13 30 1 9 3 7 7 20 12 2 44 6 9 44 31 17 20 33 18 48 23 19 31 24 50 43 15 63 */
$T2$(Loj2071):
一棵$N+1$个点的有根树,每个点有贡献$P_i$和消耗$S_i$,在树上选择$K$个点使得$$frac {sum_{i=1}^{K} {P_i}} {sum_{i=1}^{K} {S_i}}$$最大,其中选择这个点的前提条件是它的父亲必须被选,根节点默认被选,其贡献和消耗均为0。
${N,Kleq2500,P_i,S_ileq10^4}$。
$Solution$:
既然是树上选点问题,我们可以考虑树形$dp$。但我们发现直接维护比值可能不满足最优性。
比如$(20,1),(10000,1000)$在$P_i$和$S_i$很小的时候一定是选第二个更优的。
但如果我们已知这个比值是$k$,问题就转化成是否有$$frac {sum_{i=1}^{K} {P_i}} {sum_{i=1}^{K} {S_i*k}}geq0$$的选择方案存在。此时我们可以使用树上背包判断比值$k$是否可以成为答案。
那么若存在$k$可以成为答案,则$[0,k)$间所有比值均可成为答案。
反之,若$k$不能成为答案,则$(k,+∞]$间所有比值都不可能成为答案。
显然$k$满足单调性,我们可以二分答案然后$check$。
这种方法有一个专有名词叫做“分数规划”。
傻逼型树上背包$check$的复杂度为$O(N*K*K)$,我们考虑一些优化。
可以发现转移顺序是原树的$dfs$序,$dfs$序的感性描述大概是:
根在第一个,然后每棵子树按序排列,在每棵子树中,根在第一个,该子树的每棵子树按序排列……
那么在$dfs$序的序列上,显然这个点如果不取可以直接跳过以它为根的一整棵子树,
设该子树大小为$size(i)$则有$dp(i+size_i,j)=max{dp(i,j)}$。
如果取,原来是按照$dfs$序转移的,现在也只需要将这个点在$dfs$序列中的后一个点更新即可。
则有$dp(i+1,j+1)=max{dp(i,j)+P_i-k*S_i}$。
注意上面的$dp(i.j)$表示现在需要考虑第$i$个点的取舍情况,前$i-1$个点已经取完并且取了$j$个。
总复杂度$O(N imes K imes log(max{P_i}))$,显然正确。
$Code$:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> using namespace std; #define MAXN 2505 #define MAXM 2505 #define INF 0x7fffffff #define ll long long #define eps 1e-5 double P[MAXN],S[MAXN],dp[MAXN][MAXM];int N,K,num; int hd[MAXN],nxt[MAXN],to[MAXN],dfn[MAXN],size[MAXN],cnt; inline int read(){ int x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } inline void addedge(int u,int v){ to[++cnt]=v,nxt[cnt]=hd[u]; hd[u]=cnt;return; } inline void dfs(int u){ dfn[num++]=u;size[u]=1; for(int i=hd[u];i;i=nxt[i]) dfs(to[i]),size[u]+=size[to[i]]; return; } inline bool check(double x){ //cout<<x<<" ok"<<endl; for(int i=1;i<=N+1;i++) for(int j=0;j<=K+1;j++) dp[i][j]=-INF; for(int i=0;i<=N;i++) for(int j=0;j<=min(i,K+1);j++){ if(dp[i][j]==-INF)continue; //if(x-0.001<0.001)cout<<P[dfn[i]]<<" "<<S[dfn[i]]<<endl;; dp[i+1][j+1]=max(dp[i+1][j+1],dp[i][j]+P[dfn[i]]-x*S[dfn[i]]); dp[i+size[dfn[i]]][j]=max(dp[i+size[dfn[i]]][j],dp[i][j]); //if(x-0.001<0.001)cout<<i<<" "<<j<<" "<<dp[i][j]<<endl; } return dp[N+1][K+1]>=eps; } int main(){ K=read(),N=read(); for(int i=1;i<=N;i++){ cin>>S[i]>>P[i]; int x=read(); addedge(x,i); } dfs(0);//for(double i=1;i<=num;i++) cout<<dfn[i]<<" "; //cout<<endl; double l=0.0,r=10000.0; while(l+eps<r){ double mid=(l+r)/2.0; if(check(mid)) l=mid; else r=mid; } printf("%.3lf ",l); return 0; }
$T3$(Loj2244):
有N个数$A_i$和运算$B_i$,运算只可能是$or,xor,and$中的一种,
现在你需要在${0...M}$间的所有正整数中选出一个数,使其经过$N$次运算后得到的值最大,求这个最大值。
$Nleq10^5,Mleq2^{30},A_ileq2^{30}$。
$Solution$:
显然每一位的运算是互相独立的,我们可以分开处理。
考虑从高位到低位贪心,该位运算后的答案能取1则尽量取1的策略,由于$2^{i}>2^{0}+2^{1}+...+2^{i-1}$所以该策略正确。
既然这个二进制位在初始数中要么是0,要么是1,那我们只需要将该位初始置为0和1然后进行$N$次运算。
- 若置为0时运算后答案为1,则该位一定为0。(既有贡献又不影响对原数大小)
- 否则若置为1时运算后答案也为0,则该位答案无论如何也为0,该位一定为0。
- 若置为1时运算后答案为1,那么若当前的原数加上这一位的1不超限则一定取1(参见上方贪心策略的证明),若超限则没法要了,取0。
注意贪心时枚举位数要从30枚举而不是从$logM$枚举,因为高位是0也有可能答案为1。
这种题为什么会是$T3$……
$Code$:
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> using namespace std; #define MAXN 100005 #define MAXM 500005 #define INF 0x7ffffff #define ll long long ll A[MAXN],B[MAXN];char str[MAXN]; inline ll read(){ ll x=0,f=1; char c=getchar(); for(;!isdigit(c);c=getchar()) if(c=='-') f=-1; for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f; } int main(){ ll N=read(),M=read(); for(ll i=1;i<=N;i++){ cin>>str;A[i]=read(); if(str[0]=='A') B[i]=1; if(str[0]=='O') B[i]=2; if(str[0]=='X') B[i]=3; } ll now=0,ans=0; for(ll pos=30;pos>=0;pos--){ ll x1=0,x2=(1<<pos); for(ll i=1;i<=N;i++){ if(B[i]==1) x1&=A[i],x2&=A[i]; if(B[i]==2) x1|=A[i],x2|=A[i]; if(B[i]==3) x1^=A[i],x2^=A[i]; } if(x1&(1<<pos)) ans+=(1<<pos); else if(x2&(1<<pos) && now+(1<<pos)<=M) ans+=(1<<pos),now+=(1<<pos); } printf("%lld ",ans); return 0; }