noip模拟54
A. 选择
每个点的 \(d\le 10\),显然要考虑一下状压..
发现某个点 \(x\) 的每个子树的贡献都可以累积到 \(x\),
但是有一些曲折的路径,这里的曲折指的是 \(lca_{u,v}\) 不是 \(u\) 或 \(v\) 中的一个.
所以考虑如何把这些路径的贡献累计起来.
发现对于一些路径,你选了答案是这么多,你不选这个路径还可以被别的路径替代.
也就是有一些边或点是闲置的.
所以可以选择把这些点记录下来转移到根.
A_code
#include<bits/stdc++.h>
using namespace std;
namespace BSS
{
#define ll int
#define lf double
#define ull unsigned ll
#define mp make_pair
#define lb lower_bound
#define ub upper_bound
#define Fill(x,y) memset(x,y,sizeof x)
#define Copy(x,y) memcpy(x,y,sizeof x)
#define File(x,y) freopen(#x,"r",stdin),freopen(#y,"w",stdout)
#define FILE(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
inline ll read()
{
ll ss=0; bool cit=1; char ch;
while(!isdigit(ch=getchar())) if(ch=='-') cit=0;
while(isdigit(ch)) ss=(ss<<3)+(ss<<1)+(ch^48),ch=getchar();
return cit?ss:-ss;
}
} using namespace BSS;
const ll N=1035;
ll m,n,ts,ans;
ll head[N],fa[N],dp[N];
ll lk[N][N];
struct I { ll u,v,L; } p[N*N];
struct II { ll u,v,nxt; } e[N<<1];
vector<ll> sp[N],son[N];
inline void add(ll u,ll v){
e[++ts].u=u,e[ts].v=v,e[ts].nxt=head[u],
head[u]=ts;
}
void dfs(ll u,ll dad){
son[u].push_back(0);
for(ll i=head[u];i;i=e[i].nxt){
if(dad==e[i].v) continue;
dfs(e[i].v,u);
son[u].push_back(e[i].v);
}
ll cnt=son[u].size()-1,U=(1<<cnt)-1;
for(ll i=1;i<=cnt;i++)
for(ll a : sp[son[u][i]]){
for(ll j=i+1;j<=cnt;j++)
for(ll b : sp[son[u][j]])
dp[(1<<i-1)|(1<<j-1)]|=lk[a][b];
dp[1<<i-1]|=lk[u][a];
}
for(ll i=1;i<=U;i++){
for(ll j=i;j;(--j)&=i)
dp[i]=max(dp[i],dp[j]+dp[i xor j]);
}
ans+=dp[U];
for(ll i=1;i<=cnt;i++){
if(dp[U xor (1<<i-1)]==dp[U])
for(ll j : sp[son[u][i]])
sp[u].push_back(j);
}
sp[u].push_back(u); Fill(dp,0);
}
signed main(){
FILE(select);
n=read(); ll u,v,L;
for(ll i=1;i<n;i++) u=read(),v=read(),add(u,v),add(v,u);
m=read();
for(ll i=1;i<=m;i++) u=read(),v=read(),lk[u][v]=1,lk[v][u]=1;
dfs(1,0);
printf("%d\n",ans),exit(0);
}
B. 表格
C. 黑白
D. 打怪
首先,我们无论是通过推式子还是打表,
都可以发现,如果没有秒杀操作的话,
那么只需要按照 \(\dfrac{a}{c}\) 从大到小拍个序就是最优方案了.
推式子比较简单,就不写了 。
考虑如何加入秒杀操作,
我们发现如果每次只秒杀一只,那么减少的受伤量都是固定的.
所以我们就可以算出每只怪单独被秒杀时的受伤减少量.
但是我们之后就可以发现这个东西比较斜率了.
于是 \(CDQ\) 分治 + \(Sort\) + 单调栈直接就可以 \(O(nlog^2)\) 做了.
只需要让左区间按照 \(c\) 升序排序,右区间按照 \(a\) 升序排序即可.
发现 \(N<=3e5\),卡卡常还是能过.
但是我们可以发现 \(CDQ\) 分治其实是一个归并的过程,
所以我们就可以选择在归并的过程中加上归并排序.
我们可以在全局下按照 \(a\) 排序,
于是就保证了最初递归到区间的时候 \(a\) 是升序的.
然后我们考虑将左右区间按照 \(\dfrac{a}{c}\) 分开.
注意这个时候我们只是保证左区间的 \(\dfrac{a}{c}\) 全部小于右区间的 \(\dfrac{a}{c}\).
且同时保证左区间 \(a\) 升序,右区间 \(a\) 升序.
同时我们优先递归左区间,
在区间递归结束的时候将区间按照 \(c\) 升序排一下,
这样我们在左区间递归结束的时候可以保证 \(c\) 升序.
然后做一个左区间贡献给右区间的斜率优化.
发现这个时候右区间的 \(a\) 我们由于没有动过,依旧升序,
所以再次递归右区间时依旧保证 \(a\) 升序.
之后我们发现整个区间做完了.
为了在以后能够为后面的右区间做贡献,我们再次按照 \(c\) 排序,和上面说的是一样的.
我们回想以上的所有过程,发现其实是归并的一个逆过程.
其实可以发现,在做 \(CDQ\) 分治的时候,考虑左区间对于右区间的贡献时,
只需要让 ta 们的相对位置满足,那么这个贡献就可以提供过去.
所以不提供存在贡献关系的我们不需要去管 ta 们的位置是什么,只需要不对全局造成负影响即可.
所以 \(CDQ\) 确实是一种比较简单理解但是却变化较多的东西.
D_code
#include<bits/stdc++.h>
using namespace std;
namespace BSS {
#define ll long long
#define ull unsigned ll
#define lf long double
#define lbt(x) (x&(-x))
#define mp(x,y) make_pair(x,y)
#define lb lower_bound
#define ub upper_bound
#define Fill(x,y) memset(x,y,sizeof x)
#define Copy(x,y) memcpy(x,y,sizeof x)
#define File(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
inline ll read() {
ll res=0; bool cit=1; char ch;
while(!isdigit(ch=getchar())) if(ch=='-') cit=0;
while(isdigit(ch)) res=(res<<3)+(res<<1)+(ch^48),ch=getchar();
return cit?res:-res;
}
} using namespace BSS;
const ll N=3e5+21;
ll m,n,ans,nowans;
ll prec[N],sufa[N],stk[N];
struct I { ll a,c,d,e,id,ck,dp; } p[N],bin[N];
auto slope=[](ll i,ll j)->lf{ return p[i].c==p[j].c ? 1e15 : 1.0*(p[i].e-p[j].e)/((lf)(p[i].c-p[j].c)); };
inline void cdq(ll l,ll r){
if(l==r) return p[l].dp=max(p[l].dp,p[l].e),void();
ll mid=(l+r)>>1,head=1,tail=0,p1=l,p2=mid+1,cnt=l;
while(p1<=mid and p2<=r){
if(p[p1].id<=mid) bin[cnt++]=p[p1++];
else bin[cnt++]=p[p2++];
}
while(p1<=mid) bin[cnt++]=p[p1++]; while(p2<=r) bin[cnt++]=p[p2++];
for(int i=l;i<=cnt;i++) p[i]=bin[i];
cdq(l,mid);
for(int i=l;i<=mid;i++){
while(head<tail and slope(stk[tail],stk[tail-1])<slope(i,stk[tail])) tail--;
stk[++tail]=i;
}
for(int i=mid+1;i<=r;i++){
// cout<<slope(stk[head+1],stk[head])<<' '<<p[i].a<<endl;
while(head<tail and slope(stk[head+1],stk[head])>=p[i].a) head++;
// cout<<i<<' '<<p[i].e<<" "<<stk[head]<<' '<<p[stk[head]].e<<' '<<p[i].a*p[stk[head]].c<<endl;
p[i].dp=max(p[i].dp,p[i].e+p[stk[head]].e-p[i].a*p[stk[head]].c);
}
cdq(mid+1,r); p1=l,p2=mid+1,cnt=l;
while(p1<=mid and p2<=r){
if(p[p1].c<p[p2].c) bin[cnt++]=p[p1++];
else bin[cnt++]=p[p2++];
}
while(p1<=mid) bin[cnt++]=p[p1++]; while(p2<=r) bin[cnt++]=p[p2++];
for(int i=l;i<=cnt;i++) p[i]=bin[i];
// for(int i=l;i<=r;i++) cout<<p[i].c<<" "; puts("");
}
signed main(){
File(fittest);
n=read(),m=read();
for(int i=1;i<=n;i++){
p[i].a=read(),p[i].d=read();
p[i].c=(p[i].d-1)/m+1;
}
sort(p+1,p+1+n,[](I i,I j)->bool{ return 1.0*i.a/((lf)i.c) > 1.0*j.a/((lf)j.c); });
for(int i=1;i<=n;i++) prec[i]=prec[i-1]+p[i].c;
for(int i=n;i>=1;i--) sufa[i]=sufa[i+1]+p[i].a;
for(int i=1;i<=n;i++) p[i].e=p[i].a*prec[i-1]+p[i].a*(p[i].c-1)+p[i].c*sufa[i+1],p[i].id=i;
// for(int i=1;i<=n;i++) cout<<p[i].a<<' '<<p[i].d<<' '<<p[i].c<<' '<<p[i].e<<endl;
for(int i=1;i<=n;i++) ans+=prec[i-1]*p[i].a+(p[i].c-1)*p[i].a;
sort(p+1,p+1+n,[](I i,I j)->bool{ return i.a==j.a ? i.id<j.id : i.a<j.a; });
cdq(1,n); nowans=ans;
for(int i=1;i<=n;i++) nowans=min(nowans,ans-p[i].dp);
printf("%lld\n",nowans),exit(0);
}