zoukankan      html  css  js  c++  java
  • [Contest on 2021.9.7] 睡着了,但不完全睡着了

    ( ext{Strange Queries})

    解法

    首先有这样的转移:

    [egin{cases}f(n,0)=f(n-1,1) \displaystyle f(n,1)=lvert a_n-a_{n-1} vert+min{f(n-1,0),f(n-1,1)}end{cases} ]

    其中 (0/1) 表示 (n) 否/是与 (n-1) 建立关系。但这题需要我们进行区间合并,这个 (mathtt{dp}) 提醒我们从两个方向来思考:维护 (mathtt{dp}) 转移方式(矩阵);改写一个可以区间合并的 (mathtt{dp})

    这里只讲区间合并的 (mathtt{dp})。类似地,我们在区间 ([l,r]) 上维护四个变量:(l,r) 两个端点都建立关系的最小值;(l,r) 两个端点中,(l)(r) 可以不 建立关系的最小值;(l,r)可以不 建立关系的最小值。这四个变量是一层一层想到的。另外,需要注意 "可以不" 并不是 "不",这导致转移时可以不考虑某些情况,因为已经被包含在限制较小的变量中了。

    具体就用一棵平衡树维护,还需要维护区间 (max,min) 来表示 (lvert a_n-a_{n-1} vert)。最开始建树的时候要先排序。

    代码

    #include <cstdio>
    #define print(x,y) write(x),putchar(y)
    
    template <class T>
    inline T read(const T sample) {
    	T x=0; char s; bool f=0;
    	while((s=getchar())>'9' or s<'0')
    		f|=(s=='-');
    	while(s>='0' and s<='9')
    		x=(x<<1)+(x<<3)+(s^48),
    		s=getchar();
    	return f?-x:x;
    }
    
    template <class T>
    inline void write(const T x) {
    	if(x<0) {
    		putchar('-');
    		write(-x);
    		return;
    	}
    	if(x>9) write(x/10);
    	putchar(x%10^48);
    }
    
    #include <cstdlib>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    
    const int maxn=2e5+5;
    
    int n,q,idx,rt;
    struct node {
    	int mn,mx,siz,ls,rs,key,val;
    	ll ans,bl,br,ba;
    	
    	void Put() {
    		printf("VALUE %d %d %d
    ",mn,mx,val);
    		printf("%lld %lld %lld %lld
    ",ba,bl,br,ans);
    	}
    } t[maxn];
    
    int NewNode(int x) {
    	t[++idx].siz=1;
    	t[idx].ls=t[idx].rs=0;
    	t[idx].key=rand();
    	t[idx].siz=1;
    	t[idx].val=t[idx].mn=t[idx].mx=x;
    	t[idx].ba=t[idx].bl=t[idx].br=0;
    	t[idx].ans=1e15;
    	return idx;
    }
    
    void Update(const node &a,const node &b,node &o) {
    	node t=o; int inc=b.mn-a.mx;
    	t.ba=min(a.bl+b.br,a.ba+b.ba+inc);
    	t.bl=min(a.bl+b.ans,a.ba+b.bl+inc);
    	t.br=min(a.ans+b.br,a.br+b.ba+inc);
    	t.ans=min(a.ans+b.ans,a.br+b.bl+inc);
    	o=t;
    }
    
    void pushUp(int o) {
    	if(!o) return;
    	int l=t[o].ls,r=t[o].rs;
    	t[o].siz=t[l].siz+t[r].siz+1;
    	t[o].mn=t[o].mx=t[o].val;
    	t[o].ba=t[o].bl=t[o].br=0;
    	t[o].ans=1e15;
    	if(l) Update(t[l],t[o],t[o]),t[o].mn=t[l].mn;
    	if(r) Update(t[o],t[r],t[o]),t[o].mx=t[r].mx;
    }
    
    void split(int o,int k,int &x,int &y) {
    	if(!o) x=y=0;
    	else {
    		if(t[o].val<=k)
    			x=o,
    			split(t[o].rs,k,t[o].rs,y);
    		else
    			y=o,
    			split(t[o].ls,k,x,t[o].ls);
    		pushUp(o);
    	}
    }
    
    int merge(int x,int y) {
    	if(!x or !y) return x|y;
    	if(t[x].key<t[y].key) {
    		t[x].rs=merge(t[x].rs,y);
    		pushUp(x);
    		return x;
    	}
    	else {
    		t[y].ls=merge(x,t[y].ls);
    		pushUp(y);
    		return y;
    	}
    }
    
    int Val[maxn];
    
    int main() {
    	srand(20210907);
    	int a,b,c; 
    	for(int T=read(9);T;--T) {
    		n=read(9),q=read(9);
    		rt=idx=0;
    		for(int i=1;i<=n;++i)	
    			Val[i]=read(9);
    		sort(Val+1,Val+n+1);
    		for(int i=1;i<=n;++i)
    			rt=merge(rt,NewNode(Val[i]));
    		while(q--) {
    			int x,y;
    			x=read(9),y=read(9);
    			if(x==1) {
    				split(rt,y-1,a,b);
    				rt=merge(merge(a,NewNode(y)),b);
    			}
    			else {
    				split(rt,y-1,a,b);
    				split(b,y,b,c);
    				b=merge(t[b].ls,t[b].rs);
    				rt=merge(merge(a,b),c);
    			}
    			print(t[rt].ans>1e14?0:t[rt].ans,'
    ');
    		}
    	}
    	return 0;
    }
    

    ( ext{[TJOI 2013] })拯救小矮人

    解法

    首先将小矮人按照 (a_i+b_i) 从小到大排序。为什么捏?不妨令 (H-(a_i+b_i)) 是第 (i) 个小矮人的逃生高度,对于相邻小矮人 (i,j),如果我们期望 两个小矮人都逃出去,那么放逃生高度越大的小矮人越优。

    但问题是有可能小矮人不逃出去更优,可以令 (dp_i) 为逃出 (i) 个小矮人后可以垒出的最大高度,这是一个 (mathcal O(n^2))(mathtt{dp})

    更优的做法是反悔贪心。先依次使可以逃出的小矮人逃出,并用优先队列记录它的 (a),到某个无法逃出的小矮人 (i) 时,我们考虑这样操作:找到队列中最大的 (a),如果踢回这个小矮人能使 (i) 逃出,且 (max a>a_i),我们就把这个小矮人 (j) 换成 (i)

    首先 (j)(i) 这段的小矮人肯定是不受影响的,如果 (j) 能够使之间的小矮人逃出就早换了。对于之后的小矮人,垒出的高度变高了,解也就变优了。这是 (mathcal O(nlog n)) 的。

    代码

    #include <cstdio>
    #define print(x,y) write(x),putchar(y)
    
    template <class T>
    inline T read(const T sample) {
    	T x=0; char s; bool f=0;
    	while((s=getchar())>'9' or s<'0')
    		f|=(s=='-');
    	while(s>='0' and s<='9')
    		x=(x<<1)+(x<<3)+(s^48),
    		s=getchar();
    	return f?-x:x;
    }
    
    template <class T>
    inline void write(const T x) {
    	if(x<0) {
    		putchar('-');
    		write(-x);
    		return;
    	}
    	if(x>9) write(x/10);
    	putchar(x%10^48);
    }
    
    #include <queue>
    #include <algorithm>
    using namespace std;
    
    const int maxn=2005;
    
    int n;
    struct node {
    	int a,b;
    	
    	bool operator < (const node &t) const {
    		return a+b<t.a+t.b;
    	} 
    } s[maxn];
    priority_queue <int> q; 
    
    int main() {
    	n=read(9);
    	for(int i=1;i<=n;++i)
    		s[i]=(node){read(9),read(9)};
    	sort(s+1,s+n+1);
    	int h=read(9);
    	long long sum=0; int ans=0;
    	for(int i=1;i<=n;++i)
    		sum+=s[i].a;
    	for(int i=1;i<=n;++i)
    		if(sum+s[i].b>=h) {
    			sum-=s[i].a,++ans;
    			q.push(s[i].a);
    		}
    		else {
    			if(!q.empty() and sum+q.top()+s[i].b>=h and q.top()>s[i].a) {
    				sum+=q.top()-s[i].a;
    				q.pop();
    				q.push(s[i].a);
    			}
    		}
    	print(ans,'
    ');
    	return 0;
    }
    

    ( ext{[ICPC World Finals 2019] Hobson })的火车

    题目描述

    给你多棵内向基环树,对于每个点,求出有多少个点走 (le k) 步能走到这个点。

    解法

    感觉自己的脑子里根本不存在差分这个东西,好几次想用树链剖分硬艹了…

    先找出环,( m dfs) 每个环上点的子树,用一个栈动态维护这个节点到根的节点,这样就可以找到走 (k+1) 才能走到的那个祖先。

    然后考虑点对环的贡献。考虑破环为链,随便选一个点作为 (rt_0)(链的起点)。如果能贡献整个环,就将 (rt_0) 的差分数组加一,反之计算出它在环上贡献的一段区间 ([l,r]),需要注意有 (l>r) 的情况,这时需要将 (rt_0) 的差分数组加一。

    代码

    #include <cstdio>
    #define print(x,y) write(x),putchar(y)
    
    template <class T>
    inline T read(const T sample) {
    	T x=0; char s; bool f=0;
    	while((s=getchar())>'9' or s<'0')
    		f|=(s=='-');
    	while(s>='0' and s<='9')
    		x=(x<<1)+(x<<3)+(s^48),
    		s=getchar();
    	return f?-x:x;
    }
    
    template <class T>
    inline void write(const T x) {
    	if(x<0) {
    		putchar('-');
    		write(-x);
    		return;
    	}
    	if(x>9) write(x/10);
    	putchar(x%10^48);
    }
    
    #include <vector>
    using namespace std;
    
    const int maxn=5e5+5; 
    
    int n,k,f[maxn],ans[maxn];
    int vis[maxn],stk[maxn],tp;
    int dep[maxn],siz;
    vector <int> e[maxn],rt;
    
    void dfs(int u,int root) {
    	if(!vis[u]) vis[u]=1;
    	stk[++tp]=u;
    	if(tp>k+1)
    		if(stk[tp-k-1]^rt[root])
    			--ans[stk[tp-k-1]];
    	for(auto v:e[u]) {
    		if(vis[v]==2) continue;
    		dep[v]=dep[u]+1;
    		dfs(v,root);
    		if(u^rt[root])
    			ans[u]+=ans[v];
    	}
    	--tp;
    	if(dep[u]-1<=k) {
    		if(dep[u]-1+siz>k+1) {
    			++ans[rt[root]];
    			--ans[rt[(root+k-dep[u]+2+siz)%siz]];
    			if((root+k-dep[u]+2+siz)%siz<=root)	
    				++ans[rt[0]];
    		}
    		else ++ans[rt[0]];
    	}
    	if(rt[root]^u) ++ans[u];
    }
    
    void work(int x) {
    	rt.clear();
    	while(vis[x]^2) {
    		if(vis[x])
    			rt.push_back(x);
    		++vis[x]; x=f[x];
    	}
    	siz=rt.size();
    	for(int i=0;i<rt.size();++i)
    		dep[rt[i]]=1,dfs(rt[i],i);
    	for(int i=1;i<rt.size();++i)
    		ans[rt[i]]+=ans[rt[i-1]];
    }
    
    int main() {
    	n=read(9),k=read(9);
    	for(int i=1;i<=n;++i) {
    		f[i]=read(9);
    		e[f[i]].push_back(i);
    	}
    	for(int i=1;i<=n;++i)
    		if(!vis[i]) work(i);
    	for(int i=1;i<=n;++i)
    		print(ans[i],'
    ');
    	return 0;
    }
    

    简单题

    题目描述

    有两个长度为 (K) 的数列 (a,b),满足 (sum a_i=N,sum b_i=M)

    定义权值 (P)

    [prod min{a_i,b_i} ]

    你需要求出所有可能数列组合的权值之和。取模 (998244353)

    多组数据,(Tle 100,N,M,Kle 5cdot 10^5)

    解法

    定义 (dp_{i,j,k}) 为在第 (i) 位,数列 (a) 数字和为 (j),数列 (b) 数字和为 (k) 的权值之和。那么有:

    [dp_{i,j,k}=dp_{i,j,k}+dp_{i-1,j-x,k-y}cdot min{x,y} ]

    这是 (mathcal O(n^4k)) 的。具体优化可以枚举 (min{x,y}),后两维的前缀和即可,应该还要再容斥一下,就有 (mathcal O(n^3k)) 了。


    我们可以思考权值的组合意义 —— 定义 ({c}) 满足 (forall i,c_ile min{a_i,b_i}),那么对于固定的 ({a},{b}),所有合法的 ({c}) 的个数即为权值!

    转化一下,对于每种 ({c}),计算 ({a},{b}) 的种类数。

    枚举 (s=sum c_i),为了保证条件成立,可以先令 (c_i=a_i=b_i),再将 (N-s)(M-s) 填入 (K) 个位置(插板法),就有:

    [ ext{Ans}=sum_{s=0}^{min{N-K,M-K}}inom{s+K-1}{K-1}cdot inom{N-s-1}{K-1}cdot inom{M-s-1}{K-1} ]

    注意初始时先将 (1) 填入每一个位置,所以后面两个组合数的上面本来还要加个 (K),就抵消掉了。

  • 相关阅读:
    SpringBoot整合Redis---Jedis版
    java五行代码导出Excel
    Java按模板导出Excel———基于Aspose实现
    Java反射机制调用对象的方法 —— 将一个对象的属性值赋值给另一个对象的属性
    Spring+SpringMVC+JDBC实现登录
    学期总结
    C语言I博客作业09
    C语言I博客作业08
    C语言I博客作业07
    C语言I博客作业05
  • 原文地址:https://www.cnblogs.com/AWhiteWall/p/15240625.html
Copyright © 2011-2022 走看看