xj膜你赛(n-1)
Strategy
1.1 Background
针针喜欢玩一款叫做 DotA (Defense of the Algorithm) 的游戏。——CTSC2018 Day1 T1 假面
1.2 Description
继 8102CSTC 之后,DotA 推出了新的游戏模式。在一场对战中,针针需要面对 \(n(1 ≤n ≤ 4000)\) 个敌人,对于第 i 个敌人针针有如下技能可以选择 (必须选择其中一种):
- 花费 \(attack_i\) 的代价主动进攻,迫使敌人进入防御状态,使之无法进攻,但是在一场对战中只能攻击 \(k\) 次。
- 花费 \(defend_i\) 的代价防御防御该敌人的进攻,该技能可以在对战中使用任意多次。
- 与该敌人结盟。注意,结盟只能对 1 个敌人使用,此时既不能攻击该敌人,也不必防御该敌人的进攻。
现在给定每个敌人的 \(attack\) 和 \(defend\) , 以及最多攻击次数 \(k\), 针针想要知道, 在和第 \(i\) 个敌人结盟的情况下, 他在这一轮对战中最少花费的代价。
1.3 Input Format
第一行一个正整数 \(n(1 ≤ n ≤ 4000)\),表示敌人个数。
接下来 \(n\) 行,每行两个正整数 \(attack_i(1 ≤ attack_i ≤ 10^9)\) 和 \(defend_i(1 ≤ defend_i ≤ 10^9)\) 。
1.4 Output Format
一行 \(n\) 个整数, 第 \(i\) 个数表示和第 \(i\) 个敌人结盟时的最小代价。
1.5 Sample Input
4
1 5
2 3
2 4
3 5
1.6 Sample Output
8 8 7 6
1.7 Guarantee
\(n ≤ 4000\)
分析
贪心题。
1.\(O(n^2)\)
先设所有的全部选 \(attack\) ,再暴力枚举最优方案即可。
2.\(O(n\log n)\)
统计前缀和。
本题标程的复杂度是\(O(n^2\log n)\)……
Code
1.\(O(n^2)\)
#include<cstdio>
#include<algorithm>
#define maxn 40002
using namespace std;
template<typename tp>
tp read(){
char c=getchar();
bool sgn=0;
tp x=0;
while((c<'0'||c>'9')&&c!='-')c=getchar();
if(c=='-')sgn=1,c=getchar();
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+c-'0';
c=getchar();
}
return sgn?-x:x;
}
template<typename tp>
void write(const tp x){
if(x<0)putchar('-'),write(-x);
else{
write(x/10);
putchar(x%10+'0');
}
}
int n,k;
long long a[maxn],sum;
struct data{
long long val;
int pos;
bool operator <(const data& dat)const{
return val<dat.val;
}
}d[maxn];
int main(){
n=read<int>(),k=read<int>();
for(int i=1;i<=n;i++){
a[i]=read<long long>();
d[i].val=read<long long>()-a[i];
d[i].pos=i;
sum+=a[i];
}
sort(d+1,d+n+1);
for(int i=1;i<=n;i++){
long long res=sum-a[i];
int j=1,cnt=1;
for(;cnt<n-k;j++){
if(d[j].pos!=i)res+=d[j].val,cnt++;
}
for(;j<=n;j++){
if(d[j].pos!=i){
if(d[j].val<0)res+=d[j].val;
else break;
}
}
printf("%lld ",res);
}
return 0;
}
2.\(O(n\log n)\)
#include<cstdio>
#include<algorithm>
#define maxn 4005
#define INF 100000000000000000ll
using namespace std;
long long read(){
char c=getchar();
bool sgn=0;
long long x=0;
while((c<'0'||c>'9')&&c!='-')c=getchar();
if(c=='-')sgn=1,c=getchar();
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+c-'0';
c=getchar();
}
return sgn?-x:x;
}
void write(const long long x){
if(x<0)putchar('-'),write(-x);
else{
if(x>=10)write(x/10);
putchar(x%10+'0');
}
}
struct node{
long long val,pos;
}a[maxn];
inline bool cmp(const node& x,const node& y){
return x.val>y.val;
}
long long n,k,at[maxn],c[maxn],de[maxn],dp[maxn],sumd=0,suma=0;
bool b[maxn];
int main(){
n=read(),k=read();
for(int i=1;i<=n;i++){
at[i]=read(),de[i]=read();
a[i].val=de[i]-at[i];
a[i].pos=i;
c[i]=a[i].val;
sumd+=de[i];
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=k;i++){
if(a[i].val>0){
b[a[i].pos]=1;
suma+=a[i].val;
}
}
for(int i=1;i<=n;i++){
long long ans=sumd-de[i];
if(b[i])ans-=suma-c[i]+(a[k+1].val>0?a[k+1].val:0);
else ans-=suma;
write(ans),putchar(' ');
}
return 0;
}
Easy LCA (easy.cpp)
2.1 Background
问: 如何评价 WC2018 和 CTSC2018?
曰: 猫喜欢上树找 LCA。
猫: 我这次有备而来。
2.2 Description
猫喜欢上了你家的苹果树。你家的苹果树是一个以 \(1\) 号点为根的,节点数为 \(n(n ≤ 6*10^5)\)
的有根树。猫想要把树上所有 \(\text{LCA}\) 抓下来,但是他答应,只要你能回答他的一个问题,他就放过你的苹果树。
猫会给你一个长度为 \(n\) 的 \(1\) 到 \(n\) 的排列 \(p\),定义连续子段 \(p[l, r]\) 的权值如下:
同样,对于任意的 \(k\) 个连续节点,它们的 \(\text{LCA}\) 也可以用类似的方法求出。
于是,我们枚举每一个 \(l_i\) ,算出它左边第一个深度小于它的节点 \(l_j\) 和右边第一个深度小于它的节点 \(l_k\) ,则以 \(l_i\) 为 \(\text{LCA}\) 的所有区间的答案为 \((l_i-l_j+1)*(l_k-l_i+1)*dep[l_i]\) 。
我没发现上面的问题其实就是求一个最大子矩形,用单调栈或其他一些方法 \(O(n)\) 求解即可。
总时间复杂度 \(O(n\log n)\),瓶颈在求 \(\text{LCA}\) 。空间复杂度 \(O(n)\) 。
Code
#include<cstdio>
#include<algorithm>
#define maxn 1200010
using namespace std;
namespace FASTIO{
int read(){
char c=getchar();
bool sgn=0;
int x=0;
while((c<'0'||c>'9')&&c!='-')c=getchar();
if(c=='-')sgn=1,c=getchar();
while(c>='0'&&c<='9'){
x=(x<<3)+(x<<1)+c-'0';
c=getchar();
}
return sgn?-x:x;
}
void write(const long long x){
if(x<0)putchar('-'),write(-x);
else{
if(x>=10)write(x/10);
putchar(x%10+'0');
}
}
}
namespace TREE{
struct edge{
int to,next;
};
edge e[maxn<<1];
int head[maxn],cnte;
void add(int u,int v){
e[++cnte].to=v;
e[cnte].next=head[u];
head[u]=cnte;
}
int n;
}
using TREE::n;
namespace LCA{
int lg[maxn],fa[maxn][30],dep[maxn];
void initlg(){
for(int i=1;i<=n;i++)lg[i]=lg[i-1]+((1<<lg[i-1])==i);
}
void initdfs(int u,int last){
dep[u]=dep[last]+1;
fa[u][0]=last;
for(int i=1;(1<<i)<dep[u];i++){
fa[u][i]=fa[fa[u][i-1]][i-1];
}
for(int i=TREE::head[u];i;i=TREE::e[i].next){
int v=TREE::e[i].to;
if(v==last)continue;
initdfs(v,u);
}
}
int lca(int x,int y){
if(dep[x]<dep[y])swap(x,y);
while(dep[x]>dep[y]){
x=fa[x][lg[dep[x]-dep[y]]-1];
}
if(x==y)return x;
for(int i=lg[dep[x]];i>=0;i--){
if(fa[x][i]!=fa[y][i]){
x=fa[x][i];
y=fa[y][i];
}
}
return fa[x][0];
}
}
namespace STACK{
int l[maxn],top,left[maxn],right[maxn],st[maxn];
void work1(){
top=0;
for(int i=1;i<n;i++){
while(top&&l[i]<=l[st[top]]){
right[st[top]]=i-1;
top--;
}
st[++top]=i;
}
while(top){
right[st[top]]=n-1;
top--;
}
}
void work2(){
top=0;
for(int i=n-1;i>=1;i--){
while(top&&l[i]<l[st[top]]){
left[st[top]]=i+1;
top--;
}
st[++top]=i;
}
while(top){
left[st[top]]=1;
top--;
}
}
}
int a[maxn];
int main(){
n=FASTIO::read();
for(int i=1;i<n;i++){
int u=FASTIO::read(),v=FASTIO::read();
TREE::add(u,v);
TREE::add(v,u);
}
for(int i=1;i<=n;i++)a[i]=FASTIO::read();
LCA::initlg();
LCA::initdfs(1,0);
for(int i=1;i<n;i++)STACK::l[i]=LCA::dep[LCA::lca(a[i],a[i+1])];
STACK::work1();
STACK::work2();
long long ans=0;
for(int i=1;i<n;i++)ans+=(long long)(i-STACK::left[i]+1)*(STACK::right[i]-i+1)*STACK::l[i];
for(int i=1;i<=n;i++)ans+=LCA::dep[a[i]];
FASTIO::write(ans);
return 0;
}