再努力一次

再努力一次吧。

牛客多校第一场F.cut

对于区间“合并”,段数变化是O(n)的
在一段里面,数字都是递增或递减的
只需要维护一颗权值线段树,以及递增递减标志就可以表示完整个区间
把一段分成两段,或者把两段合成一段,本质就是线段树的合并或者分裂,都可以实现
每次询问,先把l,r分裂出来,然后中间不断合并即可

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=5000005;
set<int> ss;
struct node
{
	int l,r,len,ans;
	node (int _l,int _r,int _len,int _ans):l(_l),r(_r),len(_len),ans(_ans){}
	
	node()
	{
		l=r=ans=len=0;
	}
	node friend operator +(node a,node b)
	{
		if (!b.len) return a;
		if (!a.len) return b;
		return node(a.l,b.r,a.len+b.len,a.ans+b.ans+((a.r&1)^(b.l&1)));
	}
	void print ()
	{
		printf("%d %d %d %d\n",l,r,len,ans);
	}
}tr[N*2],seg[N/10];int num;
bool rev[N];
vector<int> Del;
int s1[N],s2[N];
int rt[N];
int n,m;
int new_node ()	
{
	int x;
	if (!Del.empty()) {x=Del.back();Del.pop_back();}
	else 			  x=++num;
	return x;
}
void del (int x)	{Del.push_back(x);s1[x]=s2[x]=0;tr[x].len=tr[x].ans=0;}
void bt (int &now,int L,int R,int x)
{
	now=new_node();
	if (L==R)
	{
		tr[now]=node(x,x,1,0);
		return ;
	}
	int mid=(L+R)>>1;
	if (x<=mid) bt(s1[now],L,mid,x);
	else 		bt(s2[now],mid+1,R,x);
	tr[now]=tr[s1[now]]+tr[s2[now]];
}
int Merge (int rt1,int rt2)
{
	if (rt1==0||rt2==0) return rt1|rt2;
//	printf("%d %d\n",rt1,rt2);
	s1[rt1]=Merge(s1[rt1],s1[rt2]);
	s2[rt1]=Merge(s2[rt1],s2[rt2]);
	tr[rt1]=tr[s1[rt1]]+tr[s2[rt1]];
	del(rt2);
	return rt1;
}
void modify (int now,int l,int r,int x)
{
	if (l==r)
	{
		seg[now]=tr[rt[x]];
		if (rev[x])	swap(seg[now].l,seg[now].r);
		return ;
	}
	int mid=(l+r)>>1;
	if (x<=mid) modify(now<<1,l,mid,x);
	else 		modify(now<<1|1,mid+1,r,x);
	seg[now]=seg[now<<1]+seg[now<<1|1];
}
node query (int now,int L,int R,int l,int r)
{
	if (L==l&&R==r)	return seg[now];
	int mid=(L+R)>>1;
	if (r<=mid) return query(now<<1,L,mid,l,r);
	else if (l>mid) return query(now<<1|1,mid+1,R,l,r);
	else return query(now<<1,L,mid,l,mid)+query(now<<1|1,mid+1,R,mid+1,r);
}
void split (int rt1,int &rt2,int k,int Rev)
{
	if (tr[rt1].len==k)  return ;
	rt2=new_node();
	if (Rev==0)
	{
		if (k>tr[s1[rt1]].len) split(s2[rt1],s2[rt2],k-tr[s1[rt1]].len,Rev);
		else
		{
			swap(s2[rt1],s2[rt2]);
			split(s1[rt1],s1[rt2],k,Rev);
		}
	}
	else
	{
		if (k>tr[s2[rt1]].len) split(s1[rt1],s1[rt2],k-tr[s2[rt1]].len,Rev);
		else
		{
			swap(s1[rt1],s1[rt2]);
			split(s2[rt1],s2[rt2],k,Rev);
		}
	}
	tr[rt1]=tr[s1[rt1]]+tr[s2[rt1]];
	tr[rt2]=tr[s1[rt2]]+tr[s2[rt2]];
}
set<int> :: iterator SP(int x)
{
	auto now=ss.lower_bound(x);
	if (*now==x) return now;
	now--;
	split(rt[*now],rt[x],x-*now,rev[x]=rev[*now]);
	modify(1,1,n,*now);
	modify(1,1,n,x);
	return ss.insert(x).first;
}
int main()
{
	scanf("%d%d",&n,&m);
	ss.insert(n+1);
	for (int u=1;u<=n;u++)
	{
		int x;
		scanf("%d",&x);
		bt(rt[u],1,n,x);
		modify(1,1,n,u);
		ss.insert(u);
	}
	while (m--)
	{
		int op,l,r;
		scanf("%d%d%d",&op,&l,&r);
		auto L=SP(l),R=SP(r+1);
		if (op<=2)
		{
			L++;
			for (auto i=L;i!=R;i++)
			{
			//	tr[rt[*i]].print();
			//	tr[rt[l]].print();
				rt[l]=Merge(rt[l],rt[*i]);
				modify(1,1,n,*i);
				//printf("Merge:%d %d\n",l,*i);
			}
		//	tr[rt[l]].print();
			rev[l]=op-1;
			modify(1,1,n,l);
			ss.erase(L,R);
		}
		else
		{
			printf("%d\n",query(1,1,n,l,r).ans+1);
		}
	}
	return 0;
}

codeforces 1726F. Late For Work

可以发现,花在路上的时间,不管等不等灯都一定要花的
因此,我们可以前把每个路口当前的时间加上路程时间,然后变成路上都不需要花时间
然后每一个灯,能通过的时间\([L[i],R[i])\),显然有\(L[i]=-c[i],R[i]=L[i]+g[i]\)
同时这也就是说我们每一个灯会拦住一段时间,前面来的人都会在这里被拦住
我们设\(f_i\)表示在第i个路口等了灯往后还要多久
这个显然是会走到下一个灯的地方,然后一直到那个灯的L才能走
相当于我们每一次要插入一段时间,表示这段时间都要等某一个灯
然后支持往后询问我要等哪一个灯
set对区间进行维护即可

codeforces 1065F

题意是任意一个叶子,所以可以用某一个浅的叶子刷
考虑dp,\(f_x\)表示从子树最后可以回到根,\(g_x\)表示不需要回到根
\(f\)只可以从\(f\)转移过来,并且需要满足该子树里面有一个可以到达父亲的节点来刷
\(g\)则可以再\(f\)的基础上再选择一个\(g\),表示最后停在这个子树里面

codeforces 730I

I. Olympiad in Programming and Sports
n只有3000,是一个可以暴力跑费用流的范围。
但可以通过模拟费用流优化
每一条增广路只有以下四种情况:
1.选择一个A
2.选择一个B
3.选择一个A,并将一个A换为B
4.选择一个B,并将一个B换为A
用四个堆分别维护这四个操作即可

gym102538 E

easywin
之前在随缘更新题解里面写过一次。
但当时偷懒没有写代码。
照着当时写的思路把坑补上了。
亲测写得还是很清晰的

codeforce865D

cf865D
我们可以让某一天可以同时买或同时卖,这样相互抵消就可以了
因此可以先假设每一天都买
然后考虑是否要卖出,如果卖出了,那么一定是选择之前最便宜的一个
值得注意的是,如果我们再某一天选择了卖出,并借此换掉了之前的一支股票。那么我们再这一天其实是可以买两次的,可以理解为反悔当天买入的操作。

种树

种树
题目大意:有n个数,要你在里面不超过选择k个,要求不能有选择的数相邻。问你最大价值是多少。
题解:
经典老番了。
先介绍一个经典的贪心思路:
如果我们选择了i,那么i-1和i+1要么同时选,要么同时不选。因为如果只选了一个显然不如选会i优。
因此便有了贪心的思路,选完i后,把i,i+1,i-1合并成a[i+1]+a[i-1]-a[i]。然后重复子问题即可。
再介绍一个费用流的思路:
两排点,第一排为奇数的间隔,第二排为偶数的间隔。两排点之间以具体的数来连边。然后跑流量为k的费用流即可。
考虑模拟费用流,可以发现,每一次我们一定会选择一段 (不选,选,不选,选,不选),然后集体大反转
可以得到和上面贪心一模一样的做法。

posted @ 2022-07-29 09:39  Als123  阅读(65)  评论(0编辑  收藏  举报