[LibreOJ 2472][九省联考2018]IIIDX
一直没有填的坑……今天乱写了写跑过了然后厚颜无耻凑一篇题解
还有样例过于草(出题人钦点淫梦运营)
考虑从小到大枚举序列的每一位置\(p\),然后我们发现每一位置的值的合法性事单调的(小的合法,大了就不合法),这样可以对每一个位置二分答案。
然后我们考虑二分完了答案\(x\)怎么判定。把值离散化一下然后用一个线段树来维护\(T_i\)(表示当前还没有被预定的大于等于\(i\)的数有多少个),然后判定答案就看到\(x\)的前缀区间是否都不小于\(p\)所在子树的大小。同时注意顺次枚举每个点的时候要去除他对他父亲的影响,然后每次一个点确定是什么值了也要修改对应的前缀区间。
代码:
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <functional>
#include <utility>
using R = long double;
const int maxn = 500005;
const int maxno = maxn << 2;
int n; R k;
int minv[maxno], addv[maxno];
void maintain(int o) {
int lc = o << 1, rc = o << 1 | 1;
minv[o] = std::min(minv[lc], minv[rc]);
}
void paint(int o, int v) {
addv[o] += v; minv[o] += v;
}
void pushdown(int o) {
if(addv[o] != 0) {
int lc = o << 1, rc = o << 1 | 1;
paint(lc, addv[o]); paint(rc, addv[o]);
addv[o] = 0;
}
}
int left[maxn];
void build_tree(int o, int L, int R) {
// addv[o] = 0;
if(L == R) {
minv[o] = left[L];
} else {
int M = (L + R) / 2;
build_tree(o << 1, L, M);
build_tree(o << 1 | 1, M + 1, R);
maintain(o);
}
}
void update(int o, int L, int R, int ql, int qr, int v) {
if(ql <= L && R <= qr) {
paint(o, v);
} else {
pushdown(o);
int M = (L + R) / 2;
if(ql <= M) update(o << 1, L, M, ql, qr, v);
if(qr > M) update(o << 1 | 1, M + 1, R, ql, qr, v);
maintain(o);
}
}
const int INF = 0x7f7f7f7f;
int query_min(int o, int L, int R, int ql, int qr) {
if(ql <= L && R <= qr) {
return minv[o];
} else {
pushdown(o);
int M = (L + R) / 2, ans = INF;
if(ql <= M) ans = std::min(ans, query_min(o << 1, L, M, ql, qr));
if(qr > M) ans = std::min(ans, query_min(o << 1 | 1, M + 1, R, ql, qr));
return ans;
}
}
int d[maxn], d2[maxn];
int lsiz;
void process() {
std::sort(d + 1, d + 1 + n);
std::sort(d2 + 1, d2 + 1 + n);
lsiz = std::unique(d2 + 1, d2 + 1 + n) - d2 - 1;
for(int i = 1; i <= n; i ++) {
d[i] = std::lower_bound(d2 + 1, d2 + 1 + lsiz, d[i]) - d2;
left[d[i]] ++;
}
for(int i = lsiz - 1; i >= 1; i --) {
left[i] += left[i + 1];
}
build_tree(1, 1, lsiz);
}
int A[maxn], siz[maxn];
void solve() {
for(int i = n; i >= 1; i --) {
siz[i] ++;
int f = (int)floor((R(i)) / k);
siz[f] += siz[i];
}
for(int i = 1; i <= n; i ++) {
int f = (int)floor((R(i)) / k);
if(f > 0) {
update(1, 1, lsiz, 1, A[f], siz[i]);
}
int L = (f == 0) ? 1 : A[f], R = lsiz, ret;
while(true) {
if(R - L <= 3) {
for(int j = R; j >= L; j --) {
if(query_min(1, 1, lsiz, 1, j) >= siz[i]) {
ret = j; break;
}
}
break;
}
int M = L + (R - L) / 2;
if(query_min(1, 1, lsiz, 1, M) >= siz[i]) {
L = M;
} else {
R = M;
}
}
A[i] = ret; update(1, 1, lsiz, 1, ret, -siz[i]);
}
}
int main() {
scanf("%d%Lf", &n, &k);
for(int i = 1; i <= n; i ++) {
scanf("%d", &d[i]); d2[i] = d[i];
}
process(); solve();
for(int i = 1; i <= n; i ++) {
if(i > 1) putchar(' ');
printf("%d", d2[A[i]]);
}
puts(""); return 0;
}
[BZOJ 2653]middle
前几天想用Github+org-mode替代这个博客,后来想了想,还是算了吧。毕竟博客对大家更方便。
这个题真不愧是陈老师的题啊……exciting!
首先我们看到左端点在[tex][a,b][/tex],右端点在[tex][c,d][/tex],一股GSS5的气味扑来?
为了方便,我们先将序列离散化。然后我们将序列中大于等于x的值变为1,小于x的值变为-1,则某段区间的区间和若大于等于0,则该区间的中位数大于等于x,反之则其中位数小于x(注意,此题的中位数定义比较奇怪)。
然后我们可以对每个x建一棵树,然后二分答案,对于每个答案在对应的树上跑一遍GSS5即可(不过这题[tex]a<b<c<d[/tex],所以不需要复杂的分类讨论)。
但是如果每个x都建一棵树,肯定会MLE吧?这个时候就要用主席树了= =
代码:
/**************************************************************
Problem: 2653
User: danihao123
Language: C++
Result: Accepted
Time:2220 ms
Memory:1956 kb
****************************************************************/
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn=20005;
struct Node{
Node *lc,*rc;
int maxPSum,maxSSum,sum;
Node(int x){
lc=rc=NULL;
maxPSum=maxSSum=sum=x;
}
Node(Node *l,Node *r){
lc=l;rc=r;
}
void maintain(){
maxPSum=max(lc->maxPSum,lc->sum+rc->maxPSum);
maxSSum=max(rc->maxSSum,rc->sum+lc->maxSSum);
sum=lc->sum+rc->sum;
}
};
Node* build_tree(int L,int R){
if(L==R){
return new Node(1);
}
int M=L+(R-L)/2;
Node *ans=new Node(build_tree(L,M),build_tree(M+1,R));
ans->maintain();
return ans;
}
int p,v;
Node* insert(Node *o,int L,int R){
if(L==R){
return new Node(v);
}else{
Node *ans=new Node(o->lc,o->rc);
int M=L+(R-L)/2;
if(p<=M){
ans->lc=insert(ans->lc,L,M);
}else{
ans->rc=insert(ans->rc,M+1,R);
}
ans->maintain();
return ans;
}
}
int ql,qr;
Node queryPSum(Node *o,int L,int R){
if(ql<=L && R<=qr){
return *o;
}else{
int M=L+(R-L)/2;
if(qr<=M){
return queryPSum(o->lc,L,M);
}else{
if(ql<=M){
Node l=queryPSum(o->lc,L,M);
Node r=queryPSum(o->rc,M+1,R);
Node ans(l.sum+r.sum);
ans.maxPSum=max(l.maxPSum,l.sum+r.maxPSum);
return ans;
}else{
return queryPSum(o->rc,M+1,R);
}
}
}
}
Node querySSum(Node *o,int L,int R){
if(ql<=L && R<=qr){
return *o;
}else{
int M=L+(R-L)/2;
if(qr<=M){
return querySSum(o->lc,L,M);
}else{
if(ql<=M){
Node l=querySSum(o->lc,L,M);
Node r=querySSum(o->rc,M+1,R);
Node ans(l.sum+r.sum);
ans.maxSSum=max(r.maxSSum,r.sum+l.maxSSum);
return ans;
}else{
return querySSum(o->rc,M+1,R);
}
}
}
}
int querySum(Node *o,int L,int R){
if(ql<=L && R<=qr){
return o->sum;
}else{
int M=L+(R-L)/2;
int ans=0;
if(ql<=M){
ans+=querySum(o->lc,L,M);
}
if(qr>M){
ans+=querySum(o->rc,M+1,R);
}
return ans;
}
}
Node *root[maxn];
int n;
int A[maxn],A2[maxn];
vector<int> V[maxn];
int lsh_siz;
inline void lsh(){
sort(A2+1,A2+1+n);
lsh_siz=unique(A2+1,A2+1+n)-A2-1;
for(register int i=1;i<=n;i++){
A[i]=lower_bound(A2+1,A2+1+lsh_siz,A[i])-A2;
V[A[i]].push_back(i);
}
}
int q[4];
inline bool Check(int M){
register int l,m,r;
if(q[1]+1<=q[2]-1){
ql=q[1]+1;
qr=q[2]-1;
m=querySum(root[M],1,lsh_siz);
}else{
m=0;
}
ql=q[0];qr=q[1];
l=querySSum(root[M],1,lsh_siz).maxSSum;
ql=q[2];qr=q[3];
r=queryPSum(root[M],1,lsh_siz).maxPSum;
#ifdef DEBUG
printf("Case %d: %d %d %d\n",M,l,m,r);
#endif
return l+m+r>=0;
}
int main(){
register int i,j,L,M,R,ans=0;
int t;
scanf("%d",&n);
for(i=1;i<=n;i++){
scanf("%d",&A[i]);
A2[i]=A[i];
}
lsh();
root[1]=build_tree(1,n);
for(i=2;i<=lsh_siz;i++){
root[i]=root[i-1];
for(j=0;j<V[i-1].size();j++){
p=V[i-1][j];
v=-1;
root[i]=insert(root[i],1,lsh_siz);
}
}
scanf("%d",&t);
while(t--){
for(i=0;i<4;i++){
scanf("%d",&q[i]);
q[i]=(q[i]+ans)%n;
}
sort(q,q+4);
for(i=0;i<4;i++){
q[i]++;
#ifdef DEBUG
printf("%d ",q[i]);
if(i==3){
puts("");
}
#endif
}
L=1;R=lsh_siz;
while(true){
if(R-L<=3){
for(i=R;i>=L;i--){
if(Check(i)){
ans=A2[i];
break;
}
}
break;
}
M=L+(R-L)/2;
if(Check(M)){
L=M;
}else{
R=M;
}
}
printf("%d\n",ans);
}
return 0;
}
[CodeVS 1217]借教室
很容易想到线段树水过。
很可惜,这样估计也就70分。
然后这个问题是符合单调性的……可以二分答案哒。不过怎么判定?
不知道诸位还记得差分序列?记得就好,用差分序列的方法维护即可。
等等,我怎么感觉你这个做法是[tex]O(nlogn)[/tex]的,能过?
推荐一个行之有效的优化:注意一下每一次修改都是在之前的基础上反悔或前进几次,所以就可以直接在原基础上修改差分序列辣~这样效率是很高滴~
代码:
#include <cstdio>
const int maxn=1e6+5;
typedef long long ll;
ll A[maxn];
int l[maxn],r[maxn];
ll d[maxn];
ll C[maxn];
int n,m,last=0;
bool Check(int x){
register int i,cnt=0;
if(x>last)
for(i=last+1;i<=x;i++){
C[l[i]]+=d[i];
C[r[i]+1]-=d[i];
}
if(x<last)
for(i=x+1;i<=last;i++){
C[l[i]]-=d[i];
C[r[i]+1]+=d[i];
}
last=x;
for(i=1;i<=n;i++){
cnt+=C[i];
if(cnt>A[i])
return false;
}
return true;
}
int main(){
register int i,L,M,R;
scanf("%d%d",&n,&m);
for(i=1;i<=n;i++){
scanf("%lld",&A[i]);
}
for(i=1;i<=m;i++){
scanf("%lld%d%d",&d[i],&l[i],&r[i]);
}
L=1;
R=m+1;
while(L<R){
M=L+(R-L)/2;
if(Check(M)){
L=M+1;
}else{
R=M;
}
}
if(L==m+1)
puts("0");
else
printf("-1\n%d\n",L);
return 0;
}
[POJ 2455]Secret Milking Machine
这个题要求最大边最小,很明显要二分答案。
考虑验证谓词[tex]C(x)[/tex]。我们可以将所有边权小于等于[tex]x[/tex]的边加入图,然后判断是否有[tex]T[/tex]条从1到N的不重复路径。
不过这个怎么做呢?我们可以把边加入(注意我们要加入的边都是无向边),并把容量设为1,从1到N跑一遍最大流,就是不重复路径条数。
为什么可以这样呢?每个边容量只有1,最多只能让一条路径使用并“推送”到终点,所以从1到N的最大流就是不重复路径条数辣。
代码:
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
const int maxn=205,maxp=40005;
#define REP(i,n) for(i=0;i<(n);i++)
#define REP_B(i,n) for(i=1;i<=(n);i++)
#define CL_ARR(x,v) memset(x,v,sizeof(x))
namespace Dinic{
struct Edge{
int u,v,cap,flow;
};
vector<Edge> edges;
vector<int> G[maxn];
int m;
inline void AddEdge(int u,int v,int cap){
edges.push_back((Edge){u,v,cap,0});
edges.push_back((Edge){v,u,0,0});
m=edges.size();
G[u].push_back(m-2);
G[v].push_back(m-1);
}
inline void ClearGraph(){
register int i;
edges.clear();
m=0;
REP(i,maxn){
G[i].clear();
}
}
bool vis[maxn];
int d[maxn],cur[maxn];
int s,t;
inline bool BFS(){
register int i,u;
queue<int> Q;
CL_ARR(vis,0);
Q.push(s);
d[s]=0;
vis[s]=true;
while(!Q.empty()){
u=Q.front();
Q.pop();
REP(i,G[u].size()){
Edge& e=edges[G[u][i]];
if(!vis[e.v] && e.cap>e.flow){
vis[e.v]=1;
d[e.v]=d[u]+1;
Q.push(e.v);
}
}
}
return vis[t];
}
int DFS(int x,int a){
if(x==t || a==0)
return a;
int flow=0,temp;
for(int& i=cur[x];i<G[x].size();i++){
Edge& e=edges[G[x][i]];
if(d[e.v]==d[x]+1){
temp=DFS(e.v,min(a,e.cap-e.flow));
if(temp>0){
e.flow+=temp;
edges[G[x][i]^1].flow-=temp;
flow+=temp;
a-=temp;
if(a==0)
break;
}
}
}
return flow;
}
inline int Maxflow(int S,int T){
s=S;
t=T;
register int ans=0;
while(BFS()){
CL_ARR(cur,0);
ans+=DFS(s,0x7f7f7f7f);
}
return ans;
}
};
struct Edge{
int u,v,d;
bool operator <(const Edge& x) const{
return d<x.d;
}
};
Edge EdgePool[maxp];
int n,p,t;
inline bool Check(int x){
register int i;
Dinic::ClearGraph();
REP_B(i,p){
if(EdgePool[i].d>x)
break;
Dinic::AddEdge(EdgePool[i].u,EdgePool[i].v,1);
Dinic::AddEdge(EdgePool[i].v,EdgePool[i].u,1);
}
if((Dinic::Maxflow(1,n))<t)
return false;
else
return true;
}
int main(){
register int i,L=0,M,R=0;
scanf("%d%d%d",&n,&p,&t);
REP_B(i,p){
scanf("%d%d%d",&EdgePool[i].u,&EdgePool[i].v,&EdgePool[i].d);
R=max(R,EdgePool[i].d+1);
}
sort(EdgePool+1,EdgePool+1+p);
while(L<R){
M=L+(R-L)/2;
if(Check(M))
R=M;
else
L=M+1;
}
printf("%d\n",L);
return 0;
}
[BZOJ 1196]公路修建问题
挺简单的二分答案题……然而到现在才A
思路很明显,直接二分最终答案。那么判定如何做呢?
假设判定谓词为[tex]C(x)[/tex],那么我们首先考虑选白边。贪心加入边权小于[tex]x[/tex]的边(当然是否有必要就要用并查集判定了。并且这样就算加的超过了[tex]k[/tex]条也不会对答案造成影响)。然后白边加的不够[tex]k[/tex]谓词就是假了。
然后再考虑黑边,接下来的事情就很简单了。
代码:
/**************************************************************
Problem: 1196
User: danihao123
Language: C++
Result: Accepted
Time:672 ms
Memory:1212 kb
****************************************************************/
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define REP(i,n) for(i=0;i<(n);i++)
#define RAP(i,n) for(i=1;i<=(n);i++)
const int maxn=10001,maxm=20001;
struct Edge{
int u,v,d1,d2;
};
bool cmp1(const Edge& x,const Edge& y){
return x.d1<y.d1;
}
bool cmp2(const Edge& x,const Edge& y){
return x.d2<y.d2;
}
int n;
int p[maxn],rank[maxn];
int find_set(int x){
if(p[x]==x)
return x;
else
return p[x]=find_set(p[x]);
}
inline void link_set(int x,int y){
if(rank[x]>rank[y]){
p[y]=x;
}else{
p[x]=y;
if(rank[x]==rank[y])
rank[y]++;
}
}
inline void union_set(int x,int y){
link_set(find_set(x),find_set(y));
}
inline bool is_same(int x,int y){
return find_set(x)==find_set(y);
}
inline void init_set(){
register int i;
for(i=1;i<=n;i++)
p[i]=i;
memset(rank,0,sizeof(rank));
}
int k,m;
Edge E[maxm];
inline bool check(int x){
register int i,first_cnt=0,cnt=0;
init_set();
sort(E,E+m,cmp1);
REP(i,m){
if(E[i].d1>x){
break;
}else{
if(!is_same(E[i].u,E[i].v)){
first_cnt++;
cnt++;
union_set(E[i].u,E[i].v);
}
}
if(cnt==n-1){
return true;
}
}
if(first_cnt<k)
return false;
sort(E,E+m,cmp2);
REP(i,m){
if(E[i].d2>x){
break;
}else{
if(!is_same(E[i].u,E[i].v)){
cnt++;
union_set(E[i].u,E[i].v);
}
}
if(cnt==n-1)
return true;
}
return false;
}
int main(){
register int i,L=1,M,R=0;
scanf("%d%d%d",&n,&k,&m);
m--;
REP(i,m){
scanf("%d%d%d%d",&E[i].u,&E[i].v,&E[i].d1,&E[i].d2);
R=max(R,E[i].d1);
}
R++;
while(L<R){
M=L+(R-L)/2;
if(check(M))
R=M;
else
L=M+1;
}
printf("%d\n",L);
return 0;
}
[BZOJ 4326]运输计划
很容易想到树剖直接搞一发的玩法。估计复杂度是[tex]O(nlog^2n)[/tex]的,有点吃不消,再加上会被卡常,咔嚓掉。
考虑二分答案。我们可以直接二分最终答案,那么如何验证?
假设验证谓词为[tex]C(t)[/tex],表示通过把一条边清零可以使得用时为[tex]t[/tex]。那么所有用时大于[tex]t[/tex]的计划都要被清掉一条边。可见我们需要清零的边是所有用时大于[tex]t[/tex]的计划的边的交集中的最大边。
那么如何求交集呢?可以考虑树上差分,对于每次计划[tex](u,v)[/tex],我们求出其最近公共祖先[tex]L[/tex],然后对[tex]d[u][/tex]和[tex]d[v][/tex]分别加一,对[tex]d[L][/tex]则减二。随后将d上标记上推,可以求出每个点连向父亲的边被几个计划经过。易求交集。
至此方向已明确:二分答案,树上差分验证,同时我们还要用到LCA。LCA建议使用总计[tex]O(n)[/tex]的TarjanLCA离线算法,倍增也可。
代码:
/**************************************************************
Problem: 4326
User: danihao123
Language: C++
Result: Accepted
Time:4756 ms
Memory:62188 kb
****************************************************************/
#include <cstdio>
#include <cctype>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn=300001,maxm=300001*2;
const int BUFSIZE=10000001;
#define REP(i,n) for(i=0;i<(n);i++)
#define REP_B(i,n) for(i=1;i<=(n);i++)
#define GRAPH_REP(i,u) for(i=first[(u)];i;i=next[i])
#define CUS_REP(i,a,b) for(i=(a);i<=(b);i++)
int n;
namespace FastIO{
char buf[BUFSIZE];
int IO_tot=0;
inline void InitFastIO(){
fread(buf,sizeof(char),BUFSIZE-1,stdin);
}
inline char GetC(){
return buf[IO_tot++];
}
inline int readint(){
register int x=0;
register char c=GetC();
while(!isdigit(c))
c=GetC();
while(isdigit(c)){
x=10*x+c-'0';
c=GetC();
}
return x;
}
int bf[10];
inline void putint(int x){
register int siz=0,i;
if(!x){
bf[siz++]=0;
}else{
while(x){
bf[siz++]=x%10;
x/=10;
}
}
for(i=siz-1;i>=0;i--)
putchar(bf[i]+'0');
putchar('\n');
}
};
namespace Tree{
int first[maxn];
int next[maxm],to[maxm],dist[maxm];
int tot=0;
inline void Add_Edge(int u,int v,int d){
tot++;
next[tot]=first[u];
first[u]=tot;
to[tot]=v;
dist[tot]=d;
}
int d[maxn],father[maxn];
vector<int> hx;
void dfs_1(int x,int fa,int dis){
d[x]=dis;
father[x]=fa;
int i;
GRAPH_REP(i,x){
if(to[i]!=fa)
dfs_1(to[i],x,dis+dist[i]);
}
hx.push_back(x);
}
int lazy[maxn];
inline void ClearLazy(){
memset(lazy,0,sizeof(lazy));
}
inline int DealCheck(int x){
register int i,v,ans=0;
REP_B(i,n){
v=hx[i-1];
lazy[father[v]]+=lazy[v];
}
REP_B(i,n){
if(lazy[i]==x){
ans=max(ans,d[i]-d[father[i]]);
}
}
return ans;
}
// Djoin Set
int p[maxn];
int find_set(int x){
if(p[x]==x)
return x;
else
return p[x]=find_set(p[x]);
}
inline void union_set(int x,int y){
p[find_set(y)]=find_set(x);
}
inline void make_set(int x){
p[x]=x;
}
// LCA
struct Query{
int u,v,b;
};
bool vis[maxn];
vector<Query> qs;
vector<int> querys[maxn];
inline void Add_Query(int x,int y,int b){
querys[x].push_back(qs.size());
qs.push_back((Query){x,y,b});
}
int res[maxn][3];
int n;
void LCA(int u){
make_set(u);
vis[u]=true;
int i,temp;
REP(i,querys[u].size()){
Query& tep=qs[querys[u][i]];
if(vis[tep.v])
res[tep.b][2]=find_set(tep.v);
}
for(i=first[u];i;i=next[i]){
temp=to[i];
if(!vis[temp]){
LCA(temp);
union_set(u,temp);
}
}
}
};
using namespace FastIO;
struct Deal{
int u,v,lca,d;
bool operator <(const Deal& x) const{
return d<x.d;
}
};
vector<Deal> V;
int m;
inline bool Check(int x){
register int i,ideal=0,yh=V[m-1].d;
Tree::ClearLazy();
for(i=m-1;i>=0;i--){
int& u=V[i].u,v=V[i].v,lca=V[i].lca,d=V[i].d;
if(d>x){
ideal++;
Tree::lazy[u]++;
Tree::lazy[v]++;
Tree::lazy[lca]-=2;
}else{
break;
}
}
yh-=Tree::DealCheck(ideal);
return yh<=x;
}
int main(){
register int i,u,v,lca,d;
FastIO::InitFastIO();
n=readint();
m=readint();
REP(i,n-1){
u=readint();
v=readint();
d=readint();
Tree::Add_Edge(u,v,d);
Tree::Add_Edge(v,u,d);
}
REP(i,m){
u=readint();
v=readint();
Tree::Add_Query(u,v,i);
Tree::Add_Query(v,u,i);
Tree::res[i][0]=u;
Tree::res[i][1]=v;
}
Tree::dfs_1(1,0,0);
Tree::LCA(1);
REP(i,m){
u=Tree::res[i][0];
v=Tree::res[i][1];
lca=Tree::res[i][2];
d=Tree::d[u]+Tree::d[v]-2*Tree::d[lca];
V.push_back((Deal){u,v,lca,d});
}
sort(V.begin(),V.end());
u=0;
v=V[m-1].d+1;
while(u<v){
d=u+(v-u)/2;
if(Check(d))
v=d;
else
u=d+1;
}
putint(u);
return 0;
}