【贪心&Za】
离散化
把无限空间(在OI中就是很大的范围)里面的信息,映射到
一个较小的空间里面
sort(V + 1, V + 1 + N);
M = unique(V + 1, V + 1 + N) - (V + 1);
for (r = 1 ~ N)
{
t = min(t, A[r - 1]);
Ans = max(Ans, A[r] - t);
}
一个小技巧:有若干个闭合区间\([L_i, R_i]\),把它们离散化成若干个区间
新的区间排序后也有一个序列
做法是把所有\(L_i\)和\(R_i + 1\)拿出来排序,对于相邻的两个元素可以得到一个区间\([V_i, V_i+1 − 1]\)这样每个原来的区间都包含了新的区间序列的一个区间
eg: 两个区间\([1,3],[2,5]\) 按照上面的做法离散化后变为三个新区间\([1,1],[2,3],[4,5]\) 其中\([1,1],[4,5]\)分别只被区间\([1,3],[2,5]\)覆盖 \([2,3]\)同时被两个区间覆盖 离散化出来的新区间被覆盖的情况相同 (虽然我也不知道有什么用 但是我肯定想不出来这样离散化==)
前缀与差分
普通差分
二维前缀和:\(s[x][y]=s[x-1][y]+s[x][y-1]-s[x-1][u-1]+a[x][y]\)
二维差分:对\((x_1,y_1)\sim (x_2,y_2)\) 等价于\(++D[x1][y1],--D[x1][y2+1],--D[x2+1][y1],++D[x2+1][y2+1]\)。知道D还原A做二维前缀和
for (x = 1 ~ N)
for (y = 1 ~ M)
S[x][y] = S[x - 1][y] + S[x][y - 1] - S[x - 1][y - 1] + A[x][y];
for (x = 1 ~ N)
for (y = 1 ~ M)
S[x][y] = S[x][y - 1] + A[x][y];
for (y = 1 ~ M)
for (x = 1 ~ N)
S[x][y] += S[x - 1][y];
luoguP3397地毯
二维差分+二维前缀和
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
rd(n),rd(m);
for(int i=1,x1,x2,y1,y2;i<=m;++i)
rd(x1),rd(y1),rd(x2),rd(y2),
++cf[x1][y1],++cf[x2+1][y2+1],--cf[x1][y2+1],--cf[x2+1][y1];
for(int i=1;i<=n;++i){
for(int j=1;j<=n;++j)
sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+cf[i][j],printf("%d ",sum[i][j]);
puts("");
}
return 0;
}
树上差分
松鼠的新家 模板
==不想再来一遍了 就把课件复制上来
对边差分
(u, v)上全部加上w,对于差分数组就是: u加上w,v加上w,lca减去2 × w
用子树中差分数组的和来还原信息 每个点的信息记录的是其到父亲的边的信息
对点差分
(u, v)上全部加上w,对于差分数组就是:u加上w,v加上w,lca减去w,Fatherlca减去w
同样用子树中差分数组的和来还原信息
差分和数据结构结合
对于一个支持单点修改、区间求和的数据结构,如果使用差分,
就可以支持区间加法、单点查询 甚至可以支持区间加法、区间求和
一个经典的例子就是用树状数组来完成这些事情
用DFS序还可以把放到树上,区间变成子树 感觉有点像树剖
贪心
base(?)
排序不等式:正序和不小于乱序和,乱序和不小于逆序和 调整法证明
有这么一类问题,要求一个排列,最大化/最小化一些东西
有一种贪心的思路是这样的:考虑两个元素的情况,找到一个对两个元素排列的最优方法。然后把这个方法看做排序的关键字,对排列做排序。如果可以证明两个元素的情况可以推广到多个元素,那么这个做法就是正确的
实际上一般实践的时候,后面那个证明都用对拍代替
(很多先贪心的题都是找两个元素的情况 若可以推广到多个元素 则正确
一堆经典题(?):1842 1223 1012 1080
- 微扰(邻项交换:证明在任意局面下 任何对局部最优策略的微小改变都会造成整体结果的变差 常用于以“排序”为贪心策略的证明
- 范围缩放:证明任何对局部最优策略作用范围的扩展都不会造成整体结果变差
- 决策包容性:证明在任意局面下,做出局部最优策略后,这问题状态空间中的可达集合包含了做出其他任何决策后的可达集合。换言之,这个局部最优策略提供的可能性包含其他所有策略提供的可能性
- 反证法
- 数学归纳法
调整法贪心:也可以称为带反悔的贪心 常常用到堆这样的数据结构来协助完成
[JSOI2007]建筑抢修
首先按照截止时间排序,能修就修。考虑碰到一个修不动的建筑怎么办 把之前修复时间最大的拿出来,如果比当前需要的修复时间要大,就不要修之前的那个了,转而修当前的这个 用堆维护
struct node{ll w,t;}a[N];
bool cmp(node A,node B){return A.t<B.t;}
priority_queue<int>q;
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
rd(n);
for(int i=1;i<=n;++i) rd(a[i].w),rd(a[i].t);
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;++i){
if(T+a[i].w>a[i].t){
if(a[i].w<q.top()) T-=q.top(),q.pop(),q.push(a[i].w),T+=a[i].w;
}
else T+=a[i].w,++ans,q.push(a[i].w);
}
printf("%d",ans);
return 0;
}
noip2011 观光公交
有点难受QAQ
每次修改一条路\(i\) 它只会影响到达景点\(i+1\)以及它之后的连续的会出现”人等车“的情况的景点 若景点\(i+1\)之后出现一个景点是\(x\)"车等人"的情况那么这条路权值减少就会不影响到景点\(x\)及其之后的景点
那么每次贪心减去影响最大的那条边
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
rd(n),rd(m),rd(k);
for(int i=1;i<n;++i) rd(d[i]);
for(int i=1;i<=m;++i){
rd(t[i]),rd(fr[i]),rd(to[i]);
las[fr[i]]=Max(las[fr[i]],t[i]),++dw[to[i]];
}
for(int i=1;i<=n;++i) sum[i]=sum[i-1]+dw[i];
for(int i=1,nw=0;i<=n;++i) lef[i]=nw=Max(las[i],nw),nw+=d[i];
for(int i=1;i<=m;++i) ans+=((ll)lef[to[i]-1]+d[to[i]-1])-t[i];
while(k--){
for(int i=1,nw=0;i<=n;++i) lef[i]=nw=Max(las[i],nw),nw+=d[i];
eff[n-1]=n;//第i条边改变 会影响到的最远城市
for(int i=n-2,arr;i;--i){
arr=lef[i]+d[i];
if(arr>las[i+1]) eff[i]=eff[i+1];
else eff[i]=i+1;
}
int mx=0,pos=0;
for(int i=1;i<n;++i)
if(d[i]&&sum[eff[i]]-sum[i]>mx) mx=sum[eff[i]]-sum[i],pos=i;
ans-=mx,--d[pos];
}
printf("%lld",ans);
return 0;
}
luogu3045 [USACO12FEB]牛券Cow Coupons
P3045 [USACO12FEB]牛券Cow Coupons
QAQ我太瘟辽
能用优惠券一定要用 能买便宜的一定不会买贵的。
所以先取\(c_i\)最小的前\(k\)个作为答案,这一部分物品一定会在最终答案里
证明?如果有一个不在答案里面,那么一定能通过调整找到一个更优的解
然后我们考虑用剩下的钱买剩下的物品。分两种情况考虑:
如果下一个物品不用优惠券,那么价格就是当前最小原价\(p_i\)
否则,下一个物品的优惠价格是\(c_j\),但是前面一个原来用优惠券的物品就不能再用了,所以还要加上最小的\(p_k-c_k\)
具体实现用三个堆维护
有时候我们发现,直接贪心不能得到最优解,有可能我们贪着贪着发现
之前做的决策不优了。
这时候可以考虑调整之前的策略(在上面那个问题里面就是是否使用优惠券),实现一个可以撤销的贪心,从而得到最优解。
(其实就是一个模拟费用流啦)
struct node{int a,c;}a[N];
bool cmp(node A,node B){return A.c<B.c;}
bool cmp2(node A,node B){return A.a<B.a;}
priority_queue<int,vector<int>,greater<int> >q1;
priority_queue<pii,vector<pii>,greater<pii> >q2,q3;
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
rd(n),rd(k),rd(m);ans=k;
for(int i=1;i<=n;++i) rd(a[i].a),rd(a[i].c);
sort(a+1,a+n+1,cmp);
for(int i=1;i<=k;++i){
if((sum+=a[i].c)>m) return printf("%d",i-1),0;
q1.push(a[i].a-a[i].c);
}
if(k==n) return printf("%d",n),0;
for(int i=k+1;i<=n;++i) q2.push(make_pair(a[i].a,i)),q3.push(make_pair(a[i].c,i));
for(int i=k+1;i<=n;++i){
while(use[q2.top().second]) q2.pop();
while(use[q3.top().second]) q3.pop();
int x=q2.top().second,y=q3.top().second;
int tmp1=a[x].a,tmp2=a[y].c+q1.top();
if(tmp2<tmp1)
q1.pop(),++ans,sum+=tmp2,use[y]=1,q1.push(a[y].a-a[y].c);
else ++ans,sum+=tmp1,use[x]=1;
if(sum>m) return printf("%d",ans-1),0;
}
printf("%d",n);
return 0;
}
CF1214C 1200
如果我们要移动一个右括号,那么移动到整个序列末尾一定不会比移动到其它位置更劣
而如果我们要移动一个左括号,考虑移动完后它匹配了哪一个右括号,可以发现我们不移动这个左括号,改为移动它匹配的那个括号一定也合法
所以直接拿一个栈按照括号匹配的思路扫,当发现一个多余的右括号就把它拿出来,最后到结尾了再添上
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
rd(n);
for(int i=1;i<=n;++i){
scanf("%c",&x);
if(x=='(') ++cnt,++l;
else ++r,len+=(cnt>0)*2,cnt-=(cnt>0);
}
if(l!=r) return puts("No"),0;
if(len>=n-2) puts("Yes");
else puts("No");
return 0;
}
CF 1157C 1700
首先如果左右端点的两个数不相等,那么拿较小的那一个一定更优。
如果两个数相等怎么办呢?
我们注意到在这种情况下,一定会有一端以后都不可能再被拿走了,所
以直接看从左边开始拿和从右边开始拿哪一个拿的多就行了
做的时候手工双端队列要注意细节 ==容易gg
hard code
void work(){
int ret=pre,l=L,r=R,tt1=0,tt2=0;
while(lpre) pre=a[r--],++tt1;
else break;
}
r=R,pre=ret;
while(lpre) pre=a[l++],++tt2;
else break;
}
printf("%d\n",cnt+Max(tt1,tt2));
for(int i=1;i<=cnt;++i) printf("%c",ans[i]);
if(tt1>=tt2) for(int i=1;i<=tt1;++i) printf("R");
else for(int i=1;i<=tt2;++i) printf("L");
exit(0);
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
rd(n);L=1,R=n,pre=0;
for(int i=1;i<=n;++i) rd(a[i]);
while(Lpre&&a[L]<=pre) pre=a[R--],ans[++cnt]='R';
else if(a[R]>pre&&a[L]>pre&&a[R]pre&&a[L]>pre&&a[R]>a[L]) pre=a[L++],ans[++cnt]='L';
else if(a[R]<=pre&&a[L]>pre) pre=a[L++],ans[++cnt]='L';
else if(a[R]==a[L]&&a[R]>pre) work();
if((a[L]<=pre&&a[R]<=pre)||cnt>=n) break;
}
if(L==R&&a[L]>=pre) ans[++cnt]='R';
printf("%d\n",cnt);
for(int i=1;i<=cnt;++i) printf("%c",ans[i]);
return 0;
}
CF 1175D
有一个长度为\(n\)的序列,你需要把它划分成\(k\)段,并且将第𝑖段里面的所有元素都乘\(i\)。 问最后这个序列里面所有元素的和最大是多少。
设\(p_i\)为第\(i\)段结束的位置
\(\begin{align*}sum&=\sum_{i=1}^ki*(S_{p_i}-S_{p_i-1})\\&=S_{p_1}+2S_{p_2}-2S_{p_1}+...+kS_{p_k}-kS_{p_{k-1}}\\&=kS_n-\sum_{i=1}^{k-1}S_{p_i}\end{align*}\)
所以只需要把整个数组求一个前缀和,然后在前\(n-1\)个里面选择最大的\(k-1\)个减去,得到的就是最小值
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
rd(n),rd(k);
for(int i=1,x;i<=n;++i) rd(x),s[i]=s[i-1]+(ll)x;
ans=s[n]*(ll)k;
sort(s+1,s+n);
for(int i=1;i<=k-1;++i) ans-=s[i];
printf("%lld",ans);
return 0;
}
UVA color a tree
有一棵树,需要给其所有节点染色,每个点染色所需的时间是一样的都是1.给每个点染色,还有一个开销“当前时间×\(c_i\)”,\(c_i\)是每个节点的一个权值。(当前时间是染完这个节点的时间) 染色还有另一个约束条件,要染一个点必须要先染好其父节点,所以第一个染的点是根节点. 求最小开销。
可以得知 每一个很大权值的点一定在染了它的父亲后立即就染了它
....咕咕咕==
struct node{int c,f,sz;double w;}t[N];
int find(){
double mxr=0.0;int ret;
for(int i=1;i<=n;++i)
if(i!=rt&&t[i].w>mxr) mxr=t[i].w,ret=i;
return ret;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
#endif
while(rd(n),rd(rt),ans=0,(n+rt)){
for(int i=1;i<=n;++i){
rd(t[i].c),t[i].w=(double)t[i].c;
t[i].sz=1,ans+=t[i].c;
}
for(int i=1,u,v;i<n;++i) rd(u),rd(v),t[v].f=u;
for(int i=1,pos,ff;i<n;++i){
t[pos=find()].w=0,ff=t[pos].f;
ans+=t[pos].c*t[ff].sz;
for(int j=1;j<=n;++j)
if(t[j].f==pos) t[j].f=ff;
t[ff].c+=t[pos].c,t[ff].sz+=t[pos].sz,t[ff].w=(double)t[ff].c/t[ff].sz;
}
printf("%d\n",ans);
}
return 0;
}