考试之前我刚刚领略到了特判的重要性,没想到T2的两个子任务还是写挂了,丢了20分
考试的感觉不行,一路打的都是暴力,正解的思路想到一半就断了推不下去
T1:逛公园
题目链接:
https://jzoj.net/senior/#contest/show/2534/0
题目:
琥珀色黄昏像糖在很美的远方,思念跟影子在傍晚一起被拉长……
小 B 带着 GF 去逛公园,公园一共有 n 个景点,标号为 1 . . . n。景点之间有 m 条路径相连。
小 B 想选择编号在一段区间 [l, r] 内的景点来游玩,但是如果这些景点的诱导子图形成了环,那么 GF 将会不高兴。
小 B 给出很多个询问 [x, y],想让你求有多少个区间 [l, r] 满足 x ≤ l, r ≤ y 且不会使 GF不高兴。
题解:
先来一遍tarjan把所有的点双拿出来,这张图就是环了。发现我们只需要知道环上节点编号的最大值和最小值就能判断当前区间是否包含这个环,问题转化为数轴上有一堆线段,每次询问一个区间不包含任何一条线段的方案数
我们设$lf$数组表示当前点能向左延伸的最远处,注意这个数组显然是单调增的,这个可以预处理出来。怎么预处理呢?对于每条线段$[l,r]$,我们令$lf[r]=l+1$,然后从左到右$lf[1]=1$,$lf[i]=max(lf[i],lf[i-1])(i>2)$
这样我们对于一组询问就可以统计答案了。若当前询问区间为$[x,y]$,假设其中的一个位置$i$,若满足$lf[i]>=x$,那么它对答案的贡献就是$i-lf[i]+1$;若满足$lf[i]<x$,那么对答案的贡献就是$i-x+1$
由于lf数组具有单调性,我们可以二分出一个位置i满足$lf[i]>=x,lf[i-1]<x$,对于$[i,y]$我们显然可以通过前缀和优化$O(1)$查询,对于$[x,i-1]$我们发现是一个等差数列,贡献是$\frac{(i-x+1)\times(i-x)}{2}$
当然若是二分出来的$i$大于$y$,那么$[x,y]$的任意一个区间都是合法的,这个需要特判一下
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
#include<vector>
using namespace std;
typedef long long ll;
const int N=3e5+15;
const int inf=1e9;
int n,m,tot=1,tp,tim,cnt;
int head[N],dfn[N],low[N],sta[N],lf[N];
ll rsum[N];
struct EDGE{
int to,nxt;
}edge[N<<1];
struct node{
int mx,mi;
}vd[N];
inline int read(){
char ch=getchar();int s=0,f=1;
while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*f;
}
void link(int u,int v){edge[++tot]=(EDGE){v,head[u]};head[u]=tot;}
void chkmax(int &a,int b){if (b>a) a=b;}
void chkmin(int &a,int b){if (b<a) a=b;}
void tarjan(int x,int pre){
dfn[x]=low[x]=++tim;sta[++tp]=x;
for (int i=head[x];i;i=edge[i].nxt){
int y=edge[i].to;
if (!dfn[y]){
tarjan(y,x);
chkmin(low[x],low[y]);
if (low[y]>dfn[x]) tp--;//这个必须要
if (dfn[x]==low[y]){
for (++cnt,vd[cnt].mx=vd[cnt].mi=x;sta[tp]!=x;tp--) chkmax(vd[cnt].mx,sta[tp]),chkmin(vd[cnt].mi,sta[tp]);
}
}
else if (y!=pre) chkmin(low[x],dfn[y]);
}
}
int main(){
freopen("graph.in","r",stdin);
freopen("graph.out","w",stdout);
n=read();m=read();
for (int i=1,u,v;i<=m;i++){
u=read();v=read();
link(u,v);link(v,u);
}
for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i,-1);
for (int i=1;i<=cnt;i++) lf[vd[i].mx]=vd[i].mi+1;
lf[1]=1;rsum[1]=1;
for (int i=2;i<=n;i++) chkmax(lf[i],lf[i-1]);
for (int i=1;i<=n;i++) rsum[i]=i-lf[i]+1+rsum[i-1];
int q=read();
while (q--)
{
int x=read(),y=read();
int pos=lower_bound(lf+1,lf+1+n,x)-lf;
ll re;
if (pos<=y)
{
re=rsum[y]-rsum[pos-1];
re+=1ll*(pos-x)*(pos-x+1)/2;
}
else re=1ll*(y-x+1)*(y-x+2)/2;
printf("%lld\n",re);
}
return 0;
}
T2:风筝 (CF650D)
题目链接:
https://jzoj.net/senior/#contest/show/2534/1
题目:
当一阵风吹来,风筝飞上天空,为了你,而祈祷,而祝福,而感动……
oyiya 在 AK 了 IOI 之后来到了乡下,在田野中玩耍,放松身心。
他发现前面有一排小朋友在放风筝,每一个风筝有一个高度 hi,风筝的高度可能会随着小朋友的心情而改变。这时,毒瘤的 oyiya 有了一个毒瘤的 idea,他想知道改变高度之后风筝的最长严格上升子序列。oyiya 太强了表示并不想做这种水题,你能解决这个问题吗?
题解:
推荐大佬博客:https://blog.csdn.net/zearot/article/details/50857353,感谢大佬
其实我觉得这篇博客讲的很清楚了...我再讲有点冗余
其实就是分修改后两种情况,一种是b在LIS中,一种是b不在LIS中
若b在LIS中,维护一下a之前的可以转移到b的最大值,a之后的可以从b转移到的最大值。离线加树状数组的话其实很好写,就是遇到询问就在当前树状数组里查询最大值,然后再把当前元素加进树状数组。当然还可以写可持久化线段树
若b不在LIS中,判断一下少了下标a会不会影响原来的LIS,即判断下标a是否一定要出现在原来的LIS中。首先知道一个结论,对于一个序列,若一个下标多次出现在LIS中,那么这个下标在LIS的排名一定是一样的。那么就记录一个cunt数组判断有几个位置可以胜任排名i就好了
两种情况的答案取max
离线树状数组做法
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<iostream>
#include<vector>
using namespace std;
const int N=1e6+15;
int n,m,len,tot;
int h[N],L[N],R[N],c[N],t[N],cunt[N],a[N],b[N],ANSl[N],ANSr[N];
struct node{
int ca,id;
};
vector <node> q[N];
inline int read(){
char ch=getchar();int s=0,f=1;
while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return s*f;
}
void init(){
sort(c+1,c+1+tot);
len=unique(c+1,c+1+tot)-c-1;
for (int i=1;i<=n;i++) h[i]=lower_bound(c+1,c+1+len,h[i])-c;
for (int i=1;i<=m;i++) b[i]=lower_bound(c+1,c+1+len,b[i])-c;
}
void chkmax(int &a,int b) {if (b>a) a=b;}
void modify(int x,int y){
while (x<=len){
chkmax(t[x],y);
x+=x&-x;
}
}
int query(int x){
int re=0;
while (x){
chkmax(re,t[x]);
x-=x&-x;
}
return re;
}
int main(){
freopen("kite.in","r",stdin);
freopen("kite.out","w",stdout);
n=read();m=read();
for (int i=1;i<=n;i++) h[i]=read(),c[++tot]=h[i];
for (int i=1;i<=m;i++) a[i]=read(),b[i]=read(),c[++tot]=b[i];
init();
//for (int i=1;i<=n;i++) printf("%d ",h[i]);
//printf("\n");
int k=0;
for (int i=1;i<=n;i++){
L[i]=max(1,query(h[i]-1)+1);
chkmax(k,L[i]);
modify(h[i],L[i]);
}
memset(t,0,sizeof(t));
for (int i=n;i;i--){
R[i]=query(len-h[i]+1)+1;
modify(len-h[i]+1,R[i]);
}
//for (int i=1;i<=n;i++) printf("%d %d\n",L[i],R[i]);
memset(t,0,sizeof(t));
for (int i=1;i<=m;i++) q[a[i]].push_back((node){b[i],i});
for (int i=1;i<=n;i++){
for (int j=0;j<q[i].size();j++)
ANSl[q[i][j].id]=query(q[i][j].ca-1);
modify(h[i],L[i]);
}
memset(t,0,sizeof(t));
for (int i=n;i;i--){
for (int j=0;j<q[i].size();j++)
ANSr[q[i][j].id]=query(len-q[i][j].ca);
modify(len-h[i]+1,R[i]);
}
for (int i=1;i<=n;i++) if (L[i]+R[i]==k+1) cunt[L[i]]++;
for (int i=1;i<=m;i++)
{
if (L[a[i]]+R[a[i]]!=k+1)
{
printf("%d\n",max(k,ANSl[i]+ANSr[i]+1));
}
else
{
if (cunt[L[a[i]]]>1) printf("%d\n",max(k,ANSl[i]+ANSr[i]+1));
else printf("%d\n",max(k-1,ANSl[i]+ANSr[i]+1));
}
}
return 0;
}
在线主席树做法
#include<algorithm> #include<cstring> #include<cstdio> #include<iostream> using namespace std; const int N=1e6+15; int n,m,len,tot,cnt; int h[N],L[N],R[N],c[N],t[N],cunt[N],a[N],b[N],ANSl[N],ANSr[N]; int rt[N<<4],lx[N<<4],rx[N<<4],mx[N<<4]; struct node{ int ca,id; }; inline int read(){ char ch=getchar();int s=0,f=1; while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();} while (ch>='0'&&ch<='9') {s=(s<<3)+(s<<1)+ch-'0';ch=getchar();} return s*f; } void init(){ sort(c+1,c+1+tot); len=unique(c+1,c+1+tot)-c-1; for (int i=1;i<=n;i++) h[i]=lower_bound(c+1,c+1+len,h[i])-c; for (int i=1;i<=m;i++) b[i]=lower_bound(c+1,c+1+len,b[i])-c; } void chkmax(int &a,int b) {if (b>a) a=b;} void modify(int x,int y){ while (x<=len){ chkmax(t[x],y); x+=x&-x; } } int query(int x){ int re=0; while (x){ chkmax(re,t[x]); x-=x&-x; } return re; } void pushup(int o){ mx[o]=max(mx[lx[o]],mx[rx[o]]); } int update(int pre,int l,int r,int x,int y){ int o=++cnt; if (l==r) {mx[o]=y;return o;} lx[o]=lx[pre];rx[o]=rx[pre];mx[o]=mx[pre]; int mid=l+r>>1; if (x<=mid) lx[o]=update(lx[pre],l,mid,x,y); else rx[o]=update(rx[pre],mid+1,r,x,y); pushup(o); return o; } int query1(int o,int l,int r,int x){ if (!o) return 0; if (x>=r) return mx[o]; if (l>x) return 0; int mid=l+r>>1; return max(query1(lx[o],l,mid,x),query1(rx[o],mid+1,r,x)); } int query2(int o,int l,int r,int x){ if (!o) return 0; if (x<=l) return mx[o]; if (r<x) return 0; int mid=l+r>>1; return max(query2(lx[o],l,mid,x),query2(rx[o],mid+1,r,x)); } int main(){ freopen("kite.in","r",stdin); freopen("kite.out","w",stdout); n=read();m=read(); for (int i=1;i<=n;i++) h[i]=read(),c[++tot]=h[i]; for (int i=1;i<=m;i++) a[i]=read(),b[i]=read(),c[++tot]=b[i]; init(); int k=0; for (int i=1;i<=n;i++){ L[i]=max(1,query(h[i]-1)+1); chkmax(k,L[i]); modify(h[i],L[i]); } memset(t,0,sizeof(t)); for (int i=n;i;i--){ R[i]=query(len-h[i]+1)+1; modify(len-h[i]+1,R[i]); } memset(rt,0,sizeof(rt));cnt=0; for (int i=1;i<=n;i++) rt[i]=update(rt[i-1],1,len,h[i],L[i]); for (int i=1;i<=m;i++) ANSl[i]=query1(rt[a[i]-1],1,len,b[i]-1); memset(rt,0,sizeof(rt));cnt=0; for (int i=n;i;i--) rt[i]=update(rt[i+1],1,len,h[i],R[i]); for (int i=1;i<=m;i++) ANSr[i]=query2(rt[a[i]+1],1,len,b[i]+1); for (int i=1;i<=n;i++) if (L[i]+R[i]==k+1) cunt[L[i]]++; for (int i=1;i<=m;i++) { if (L[a[i]]+R[a[i]]!=k+1) { printf("%d\n",max(k,ANSl[i]+ANSr[i]+1)); } else { if (cunt[L[a[i]]]>1) printf("%d\n",max(k,ANSl[i]+ANSr[i]+1)); else printf("%d\n",max(k-1,ANSl[i]+ANSr[i]+1)); } } return 0; }