暑期集训1

T1:玩游戏:思维+贪心

T2:排列:笛卡尔树DP(其实就是把问题通过一个方式分为左子区间和右子区间分别处理)

T3:最短路:神奇双向同时Dij最短路

T4:矩形:线段树标记和区间处理在二维问题上灵活应用

T1:给你一个数列A,和位置pos,要求从pos位置开始,双指针l,r从pos开始,每次l可以--,r可以++,要求任意时刻

满足sigma(a[i])[l<i<=r]<=0,问你是否存在一种移动顺序使得l移动到1,r移动到n

贪心策略:
从k位置开始,每次向左向右选到第一个小于当前sum的位置,如果有,就跳过去;如果没有,就不跳
在尝试完左右之后,如果都没跳,说明根本走不动,无论是左右,只要一走,就会>0,就return 0;如果没跳是因为已经到终点了,就reutrn 1
如果到终点但是最后合并不了,return 0
注意必须是选到第一个小于的就溜(其实不是第一个也行,但是我判断继续的条件就是<=0就行,所以不能及时止损),因为你不知道下一个小的到底在什么时候,不然
1
7 3
9 3 -3 -2 4 1 -20
这个数据,你左边3一旦在4之前+上,就不可能合法了
为什么这种贪心是对的:
本来我是想如果对于单点是负数我就+,是正数就不知道怎么办了,但是,无论是正数还是负数,我只关心对于当前答案的影响,如果是正数,我就往后找到第一个有负数可以抵消正数贡献的,
同时还保证过程中sum<=0合法,这就一定没问题
O(n)

const int N=100;
int t,n,k;
ll a[100000+100];
inline bool deal()
{
	int l=k+1,r=k;ll sum=0;
	while(sum<=0)
	{
		bool ok=0;
		ll lres=sum,rres=sum;ll lt=l,rt=r;
		while(lt>2&&lres>=sum)
		{
			if(lres+a[lt-1]<=0)lres+=a[lt-1],--lt;
			else break;
		}
		if(lres<sum)ok=1,sum=rres=lres,l=lt;
		while(rt<n&&rres>=sum)
		{
			if(rres+a[rt+1]<=0)rres+=a[rt+1],++rt;
			else break;
		}
		if(rres<sum)ok=1,sum=rres,r=rt;
		if(!ok)//没更新
		{
			if(lt>2||rt<n||(rres+lres-sum)>0)return false;
			//对应都没更新,(1)向左走不了(2)向右走不了(3)局部走到,但是合并不了
			return true;
		}
		if(l==2&&r==n)return true;
	}
	return 0;
}
int main()
{
	t=re();
	_f(qwertyuiop,1,t)
	{
		n=re(),k=re();
		_f(i,1,n)a[i]=re();
		if(deal())chu("Yes\n");
		else chu("No\n");
	}
	return 0;
}

T2:给你两个正整数n,k,要求你生成n的排列,对于这个排列,每次的操作是删除序列中的“相对小值”,“相对小值”的定义是对于这个数,存在和他相邻的数

>他。问你有多少种n的排列,输出方案数(n<=1000,k<=1000)

分治的思想,假如我确定了n序列的max,max把n序列分成相互独立的两个部分,两个部分不会相互影响,因为max一定是最后剩下或者最后删去的数。
对于左右的区间,还可以枚举max,分区间,直到最简单的问题。设dp[i][j][0/1][0/1]是长度是i的序列,至多j次操作后只剩最值,左区间左边界的上一位存在最大值(0),右边界下一位存在最大值(1)
,右区间同,的方案数。我们考虑合并一个两个区间变成一个。(至多是一个前缀和优化,省略掉还要枚举的q,p俩维度)。
如果区间左右边界之外没有max(原始区间),那么枚举区间内max_p位置,左子区间和右子区间的max共用max_p,不需要再次合并,因为max左右的数可以随便选
,乘上组合数C[i-1][k-1],也正是这里枚举完了放的数,所以数之间的大小已经确定了,max就是确定的,不能再枚举;
如果左边有边界,那中间的max_p必须删,而且只能再遇到左边界的max再删,所以左边界j-1,留至少一步,
如果左右都有,都可以删,容斥一下,所有的减去都是j的(一个也删不了)。
初始化注意一下 O(nlog^2n)


const int N=100;
ll dp[1010][1010][2][2],mod,C[1010][1010];
int n,m;
int main()
{
	n=re(),m=re(),mod=re();
    _f(i,0,m)dp[0][i][1][0]=dp[0][i][0][1]=dp[0][i][1][1]=dp[0][i][0][0]=1;
    C[0][0]=C[1][0]=C[1][1]=1;
    _f(i,2,n)
    _f(j,0,n)C[i][j]=(C[i-1][j]+((j>0)?(C[i-1][j-1]):(0)))%mod;

    _f(i,1,n)//长度
    _f(j,1,m)//最多的操作次数
    _f(k,1,i)//左右区间分别的长度
    {
        dp[i][j][0][0]+=dp[k-1][j][0][1]*dp[i-k][j][1][0]%mod*C[i-1][k-1]%mod;
        dp[i][j][0][0]%=mod;
        dp[i][j][1][0]+=dp[k-1][j-1][1][1]*dp[i-k][j][1][0]%mod*C[i-1][k-1]%mod;
        dp[i][j][1][0]%=mod;
        dp[i][j][0][1]+=dp[k-1][j][0][1]*dp[i-k][j-1][1][1]%mod*C[i-1][k-1]%mod;
        dp[i][j][0][1]%=mod;
        dp[i][j][1][1]+=(dp[k-1][j][1][1]*dp[i-k][j][1][1]%mod+mod-(dp[k-1][j][1][1]-dp[k-1][j-1][1][1]+mod)*(dp[i-k][j][1][1]+mod-dp[i-k][j-1][1][1])%mod)*C[i-1][k-1]%mod;
        dp[i][j][1][1]%=mod;
    }
    chu("%lld",(dp[n][m][0][0]-dp[n][m-1][0][0]+mod)%mod);
	return 0;
}

T3:给你一个有向图,每个节点有点权,问你一种遍历方法,从1到n,再从n到1,经过路径上的点权加到ans里,但是每个点###的点权只能加一次,问你

最小的ans是多少

其实问题就在于怎么去表示跑过来又跑回去的过程中,我每一个决策对应的选的点不同,那可以免费的点也不同,很难记录完全
怎么解决?其实就是我选择一种玄学的算法,决定了我当前就是最优解,这样只记录当前的唯一状态然后跑下去就行
(1)瞎搞:从正跑到反,删点,然后从反跑到正,记录ans1;默认2号点必须选,点权0,正反跑dij,反正跑dij(第一遍就边跑边珊,第二遍跑回去)
这样跑n-2次,最后答案从(n-2)2+1个答案里选
O(n^2
m)
()正解:我建立2张图,建立2个最短路状态,同时跑dij,但是共用一个遍历到的点的记录数组。
bitset g[i][j]记录正着走到i,反着走到j的经过的节点是谁
dis[i][j]记录我收获了多少ans,然后跑的时候就一块跑,非常神奇而且快速....O(nlogn)

const int N=100;
struct node
{
	int to,nxt;
}e[200000];
int tot,head[260][2],n,m;
bitset<260>g[260][260];bool vis[260][260];
int dis[260][260],a[260];
struct stu
{
	int x,y,ds;
	bool operator<(const stu&A)const
	{
		return ds>A.ds;
	}
	stu(){}
	stu(int xxx,int yyy,int dsds)
	{
		x=xxx,y=yyy,ds=dsds;
	}
}lik;
priority_queue<stu>st;
inline void Add(int x,int y,int id)
{
	e[++tot].nxt=head[x][id],e[tot].to=y;head[x][id]=tot;
}
inline void Dijstra()
{
	memset(dis,0x3f,sizeof(dis));
	lik.x=1,lik.y=n,lik.ds=a[1]+a[n];st.push(lik);
	g[1][n][1]=1;g[1][n][n]=1;
	while(!st.empty())
	{
		lik=st.top();st.pop();
		vis[lik.x][lik.y]=1;
		for(rint i=head[lik.x][0];i;i=e[i].nxt)
		{
			int to=e[i].to,redis=lik.ds;
			if(!g[lik.x][lik.y][to])redis+=a[to];
			if(dis[to][lik.y]>redis)
			{
				dis[to][lik.y]=redis;
				g[to][lik.y]=g[lik.x][lik.y];
				g[to][lik.y][to]=1;
				st.push(stu(to,lik.y,redis));
			}
		}
		for(rint i=head[lik.y][1];i;i=e[i].nxt)
		{
			int to=e[i].to,redis=lik.ds;//价值和
			if(!g[lik.x][lik.y][to])redis+=a[to];
			if(dis[lik.x][to]>redis)
			{
				dis[lik.x][to]=redis;
				g[lik.x][to]=g[lik.x][lik.y];
				g[lik.x][to][to]=1;
				st.push(stu(lik.x,to,redis));
			}
		}
		while(!st.empty()&&vis[st.top().x][st.top().y])st.pop();
	}
}
int main()
{
	n=re(),m=re();
	_f(i,1,n)a[i]=re();
	_f(i,1,m)
	{
		int ai=re(),bi=re();
		Add(ai,bi,0);Add(ai,bi,1);
	}
	Dijstra();
	if(dis[n][1]<0x3f3f3f3f)chu("%d",dis[n][1]);
	else chu("-1");
	return 0;
}

T4:给你若干个二维矩形的坐标,求这若干个矩形相交形成的联通块个数(n<1e5

正常思路:按照左端点排序,每次左边加边,右边查边,查满足纵向相交而且左边加入的边的右边界<=加的右边。这个可以用线段树维护。mark:代表当前区间是否是被连续区间覆盖(就是左儿子右儿子没有分别递归下去的不同颜色);id:当前区间如果是唯一完全覆盖,就是那个完全覆盖的编号,否则无效;max_x:当前完全覆盖的颜色的右边界,因为我只是+左,查右,左边不会删除,所以用一个边界时刻限制和删除不合法的颜色合并;tag:完全覆盖的懒度标记。最妙的一处是,我怎么解决的覆盖和连接问题,就是假如(2,4)区间被x覆盖,但是(1,1)和(3,4)分别被不同覆盖,而且右边界更大,这样我就没办法正确更新了,所以我规定无论是修改还是查询,必须在唯一颜色唯一覆盖的时候进行。最关键的是pushup和pushdown

pushup:如果左边和右边颜色一样就让mark=1,id更新,如果不一样mark=2,id无效;但是左右区间的id也可能无效,所以还要mark[lson][rson]判断一下。

pushdown:懒度标记,id和mark都更新一下,因为唯一覆盖了,所以所有零碎的区间都连在一起可以忽略了,那么max_x就取max就行

const int N=1e5+10;
int n,fa[N];
struct Rectangle{
    int r1,c1,r2,c2;
    bool operator<(const Rectangle&G)const
    {
        return r1<G.r1;
    }
}rec[N];
inline int Find(int ro)
{
    if(ro==fa[ro])return ro;
    return fa[ro]=Find(fa[ro]);
}
struct Segtree{
    int ls[N<<2],rs[N<<2],tot,max_x[N<<2],id[N<<2],tag[N<<2],mark[N<<2],root;
    Segtree(){
        memset(ls,0,sizeof(ls));
        memset(rs,0,sizeof(rs));
        memset(max_x,0,sizeof(max_x));
        memset(id,0,sizeof(id));
        memset(tag,0,sizeof(tag));
        memset(mark,0,sizeof(mark));tot=root=0;
    }
    inline void Build(int&rt,int l,int r){
        if(!rt)rt=++tot;
        mark[rt]=1;
        if(l==r)return;
        int mid=(l+r)>>1;
        Build(ls[rt],l,mid);Build(rs[rt],mid+1,r);
    }
    inline void Pushup(int rt)
    {
        if(id[ls[rt]]==id[rs[rt]]){
            max_x[rt]=max(max_x[ls[rt]],max_x[rt]);id[rt]=id[ls[rt]];
            mark[rt]=1;
            //如果是自己本身还有颜色,就不是唯一颜色覆盖了
            //但是一定是全的,所以互相覆盖等价一个,max_x取max就行
        }
        else mark[rt]=2;//否则就是多颜色覆盖或者无颜色覆盖,就是无效
        if(mark[ls[rt]]==2||mark[rs[rt]]==2)mark[rt]=2;
    }
    inline void Pushdown(int rt)
    {
        if(tag[rt]){
            tag[ls[rt]]=tag[rs[rt]]=1;tag[rt]=0;
            max_x[ls[rt]]=max_x[rs[rt]]=max_x[rt];id[ls[rt]]=id[rs[rt]]=id[rt];//完全覆盖只代表本层,mark不能改
        }//完全覆盖,那么一定都连着,所以mark不用改,就当是一个,在这一层起作用就行
    }
    inline void Update1(int rt,int l,int r,int L,int R,int x,int k)//L,R的上下界,x是最右端,k是编号
    {
        if(L<=l&&r<=R&&mark[rt]==1){
            if(max_x[rt]<x)return;
            fa[Find(k)]=Find(id[rt]);return;
        }
        if(l==r)return;
        //这里的mark代表的不是唯一覆盖,是子区间有没有部分不相交的覆盖
        int mid=(l+r)>>1;
        Pushdown(rt);
        if(L<=mid)Update1(ls[rt],l,mid,L,R,x,k);
        if(R>mid)Update1(rs[rt],mid+1,r,L,R,x,k);
    }
    inline void Update2(int rt,int l,int r,int L,int R,int x,int k)
    {
        if(L<=l&&r<=R&&mark[rt]==1){//这里不是不合法,是一定和之前矩阵交而且右边界没有贡献,直接忽略
        //如果忽略标记就不要瞎穿,因为之前pushdown可能已经解锁了标记
        if(x<max_x[rt])return;
            max_x[rt]=x;id[rt]=k;tag[rt]=1;return;
        }
        if(l==r)return;
        Pushdown(rt);
        int mid=(l+r)>>1;
        if(L<=mid)Update2(ls[rt],l,mid,L,R,x,k);
        if(R>mid)Update2(rs[rt],mid+1,r,L,R,x,k);
        Pushup(rt);
    }
}T;
int main()
{
    n=re();
    int mx_c=0;
    _f(i,1,n){
        fa[i]=i;
rec[i].r1=re(),rec[i].c1=re(),rec[i].r2=re(),rec[i].c2=re();
        mx_c=max(mx_c,rec[i].c2);
    }
    sort(rec+1,rec+1+n);T.root=T.tot=0;
    T.Build(T.root,1,mx_c);
    _f(i,1,n){
        T.Update1(T.root,1,mx_c,rec[i].c1,rec[i].c2,rec[i].r1,i);
        T.Update2(T.root,1,mx_c,rec[i].c1,rec[i].c2,rec[i].r2,i);
    }
    int cnt=0;
    _f(i,1,n)if(fa[i]==i)cnt++;
    chu("%d",cnt);
	return 0;
}
posted on 2022-08-12 21:05  HZOI-曹蓉  阅读(55)  评论(3编辑  收藏  举报