比赛链接
A、B、C、D、E1略
E2
思路
对于代价相同的边肯定取对答案贡献较大的,考虑把两种代价的边分别丢到两个优先队列里,代价为的称为堆,代价为的称为堆。对于两条边的贡献之和大于一条边的,此时选择这条边必定不如选择两条边更优,考虑选择贡献最大的一条边还是两条都选。如果当前选一条边再加一条边就可以达成目的,代价为,那么直接选择两条边显然错过了这种情况,因此每次比较只选择一条边。
另外注意一条边被取出之后会重新加入队列,所以取两条边的时候有可能是同一条边取两遍。
代码细节较多。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define LLM LONG_LONG_MAX
#define LLm LONG_LONG_MIN
#define pii pair<ll,ll>
typedef long long ll;
typedef double db;
using namespace std;
inline void In(ll _,...)
{
va_list lis;
va_start(lis,_);
while(_--)
scanf("%lld",va_arg(lis,ll*));
}
inline void Out(ll _,...)
{
va_list lis;
va_start(lis,_);
while(_--)
printf("%lld\n",va_arg(lis,ll));
}
inline void Out_(ll _,...)
{
va_list lis;
va_start(lis,_);
while(_--)
printf("%lld ",va_arg(lis,ll));
puts("");
}
const int N=1E5+10;
ll n,s,tot,sum;
ll head[N];
struct Edge{
ll nxt,to,w,ty;
}e[2*N];
struct Node{
ll w,cnt;
ll cut() const
{
return cnt*(w-w/2);
}
bool operator < (const Node &tmp) const
{
return this->cut()<tmp.cut();
}
};
priority_queue<Node> q1,q2;
inline void add_edge(ll u,ll v,ll w,ll ty,int flag)
{
e[++tot].nxt=head[u];
e[tot].to=v;
e[tot].w=w;
e[tot].ty=ty;
head[u]=tot;
if(flag)
add_edge(v,u,w,ty,0);
}
ll dfs(ll u,ll fa)
{
ll flag=0,ret=0;
bl(u,i)
{
ll v=e[i].to;
if(v==fa)
continue;
flag=1;
ll tmp=dfs(v,u);
ret+=tmp;
if(e[i].ty&1)
q1.push((Node){e[i].w,tmp});
else
q2.push((Node){e[i].w,tmp});
sum+=e[i].w*tmp;
}
if(!flag)
ret=1;
return ret;
}
void solve()
{
tot=sum=0;
while(!q1.empty())
q1.pop();
while(!q2.empty())
q2.pop();
In(2,&n,&s);
rep(i,1,n)
{
head[i]=0;
e[i]=e[n+i]=(Edge){0,0,0,0};
}
rep(i,1,n-1)
{
ll u,v,w,ty;
In(4,&u,&v,&w,&ty);
add_edge(u,v,w,ty,1);
}
dfs(1,0);
ll ans=0;
while(sum>s)
{
if(!q1.empty() && sum-q1.top().cut()<=s)
{
++ans;
break;
}
Node x=(Node){0,0},y=(Node){0,0},z=(Node){0,0};
if(!q1.empty())
{
x=q1.top();
q1.pop();
y=(Node){x.w/2,x.cnt};
if(!q1.empty() && q1.top().cut()>y.cut())
y=q1.top();
}
if(!q2.empty())
{
z=q2.top();
q2.pop();
}
if(x.cut()+y.cut()>=z.cut())
{
sum-=x.cut();
++ans;
x.w>>=1;
q1.push(x);
q2.push(z);
}
else
{
sum-=z.cut();
ans+=2;
z.w>>=1;
q2.push(z);
if(x.w)
q1.push(x);
}
}
Out(1,ans);
}
int main()
{
int _;
cin>>_;
while(_--)
{
solve();
}
}
F
思路
首先对端点离散化。区间dp,设为区间上的答案,显然它直接包含较短区间的答案,即。考虑怎么通过新的端点获得更优的答案。一条线段只有端点与重合时才可以贡献答案,假设有一条左端点在的线段,其右端点为,就有。预处理一下以某个端点作为左端点的线段有哪些即可。
代码
#include<bits/stdc++.h>
#define rep(i,st,ed) for(int (i)=(st);(i)<=(ed);++(i))
#define bl(u,i) for(int (i)=head[(u)];(i);(i)=e[(i)].nxt)
#define LLM LONG_LONG_MAX
#define LLm LONG_LONG_MIN
#define pii pair<ll,ll>
typedef long long ll;
typedef double db;
using namespace std;
inline void In(ll _,...)
{
va_list lis;
va_start(lis,_);
while(_--)
scanf("%lld",va_arg(lis,ll*));
}
inline void Out(ll _,...)
{
va_list lis;
va_start(lis,_);
while(_--)
printf("%lld\n",va_arg(lis,ll));
}
inline void Out_(ll _,...)
{
va_list lis;
va_start(lis,_);
while(_--)
printf("%lld ",va_arg(lis,ll));
}
const int N=3005;
int n,tot;
int ori[2*N],l[N],r[N],f[2*N][2*N];
vector<int> ve[2*N];
void print()
{
rep(i,1,n)
cout<<l[i]<<" "<<r[i]<<endl;
}
void solve()
{
tot=0;
scanf("%d",&n);
rep(i,1,n)
{
scanf("%d%d",l+i,r+i);
ori[++tot]=l[i];
ori[++tot]=r[i];
}
sort(ori+1,ori+tot+1);
ll m=unique(ori,ori+tot+1)-ori-1;
rep(i,1,m)
rep(j,1,m)
f[i][j]=0;
rep(i,1,m)
ve[i].clear();
rep(i,1,n)
{
l[i]=lower_bound(ori+1,ori+m+1,l[i])-ori;
r[i]=lower_bound(ori+1,ori+m+1,r[i])-ori;
f[l[i]][r[i]]=1;
ve[l[i]].push_back(r[i]);
}
rep(len,1,m)
{
for(ll i=1;i+len-1<=m;++i)
{
ll j=i+len-1;
ll tmp=f[i][j];
f[i][j]=0;
f[i][j]=max(f[i+1][j],f[i][j-1]);
for(auto k:ve[i])
{
if(k>j)
continue;
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]);
}
f[i][j]+=tmp;
}
}
printf("%d\n",f[1][m]);
}
int main()
{
int _;
cin>>_;
while(_--)
{
solve();
}
}