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(nlog n))
统计前缀和。
本题标程的复杂度是(O(n^2log 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(nlog 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))
的有根树。猫想要把树上所有 ( ext{LCA}) 抓下来,但是他答应,只要你能回答他的一个问题,他就放过你的苹果树。
猫会给你一个长度为 (n) 的 (1) 到 (n) 的排列 (p),定义连续子段 (p[l, r]) 的权值如下:
同样,对于任意的 (k) 个连续节点,它们的 ( ext{LCA}) 也可以用类似的方法求出。
于是,我们枚举每一个 (l_i) ,算出它左边第一个深度小于它的节点 (l_j) 和右边第一个深度小于它的节点 (l_k) ,则以 (l_i) 为 ( ext{LCA}) 的所有区间的答案为 ((l_i-l_j+1)*(l_k-l_i+1)*dep[l_i]) 。
我没发现上面的问题其实就是求一个最大子矩形,用单调栈或其他一些方法 (O(n)) 求解即可。
总时间复杂度 (O(nlog n)),瓶颈在求 ( ext{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;
}