• THUSC2021 题解


    自闭记

    T1 move

    Description

    给定一个长度为 (n) 的序列 (a) 和一个正整数 (m),现在按下述策略删除 (a) 中的数字:

    选出一个下标序列 (p),使得其:

    1. 单调递增
    2. (sum_{xin p}a_x le m)
    3. 元素个数最多
    4. 字典序是满足上述条件的序列中最大的

    然后删除 (a) 中下标在 (p) 中的元素。问按上述策略进行几次删除会使 (a) 为空。

    (nle 5 imes 10^4,mle 10^9)

    Solution

    如果没有第4条限制,那么只需要用 (set) 维护所有数字,然后贪心选择最小的元素即可。如果有第4条限制,通过这样方法得到的元素个数也一定是最多的,因此一定选出的下标序列大小已经确定。

    考虑从前往后确定该下标序列中的元素,对于当前位置,考虑二分,那么元素 (id) 能放在当前位置,当且仅当下标 (ge id) 且还存在的元素中,最小的 (k) 个元素之和 (le m)。 找到 (id) 之后,将 (id) 删掉,继续二分下一个元素。因此需要支持单点修改,考虑使用树套树维护这个东西,外层用树状数组或线段树维护下标区间,内层用权值线段树维护当前区间的元素的权值。

    单点修改时,在 (log) 个区间内同时修改。查询时,将询问拆为 (log) 个区间,然后将这 (log) 个区间的权值线段树合并进行查询。当然事实上你不需要合并,只需要同时维护 (log) 个根节点,正常权值线段树找左子树大小时,就将这 (log) 个根的左子树大小加起来即可。这是树套树的一种常见套路,全世界大概只有我一个不会。

    总复杂度为 (mathcal O(nlog^3 n))

    Code

    #include<bits/stdc++.h>
    using namespace std;
    const int N=5e4+10,lg=16;
    typedef long long ll;
    const int inf=0x3f3f3f3f;
    int n,m,a[N],ans,b[N],pos[N],cnt;
    
    namespace iobuff{
    	const int LEN=1000000;
    	char in[LEN+5],out[LEN+5];
    	char *pin=in,*pout=out,*ed=in,*eout=out+LEN;
    	inline char gc(void){
    		#ifdef LOCAL
    		return getchar();
    		#endif
    		return pin==ed&&(ed=(pin=in)+fread(in,1,LEN,stdin),ed==in)?EOF:*pin++;
    	}
    	inline void pc(char c){
    		pout==eout&&(fwrite(out,1,LEN,stdout),pout=out);
    		(*pout++)=c;
    	}
    	inline void flush(){fwrite(out,1,pout-out,stdout),pout=out;}
    	template<typename T> inline void read(T &x){
    		static int f;
    		static char c;
    		c=gc(),f=1,x=0;
    		while(c<'0'||c>'9') f=(c=='-'?-1:1),c=gc();
    		while(c>='0'&&c<='9') x=10*x+c-'0',c=gc();
    		x*=f;
    	}
    	template<typename T> inline void putint(T x,char div){
    		static char s[15];
    		static int top;
    		top=0;
    		x<0?pc('-'),x=-x:0;
    		while(x) s[top++]=x%10,x/=10;
    		!top?pc('0'),0:0;
    		while(top--) pc(s[top]+'0');
    		pc(div);
    	}
    }
    using namespace iobuff;
    
    int q[N];
    namespace SGT{
    	const int M=N*lg*lg;
    	#define mid ((l+r)>>1)
    	int ls[M],rs[M],siz[M],tot;
    	ll sum[M];
    	inline void update(int &p,int x,int tp,int l=1,int r=cnt){
    		if(!p) p=++tot;
    		siz[p]+=tp;sum[p]+=tp*b[x];
    		if(l==r) return ;
    		if(x<=mid) update(ls[p],x,tp,l,mid);
    		else update(rs[p],x,tp,mid+1,r);
    	}
    	inline ll query(int tot,int k,int m,int l=1,int r=cnt){
    		int sz=0,lsiz=0;ll lsum=0;
    		for(int i=1;i<=tot;++i){
    			int p=q[i];
    			sz+=siz[p],lsiz+=siz[ls[p]],lsum+=sum[ls[p]];
    		}
    		if(sz<k) return m+1;
    		if(l==r) return 1ll*k*b[l];
    		if(lsiz>=k){
    			for(int i=1;i<=tot;++i) q[i]=ls[q[i]];
    			return query(tot,k,m,l,mid);
    		} 
    		else{
    			if(lsum>m) return m+1;
    			for(int i=1;i<=tot;++i) q[i]=rs[q[i]];
    			return lsum+query(tot,k-lsiz,m-lsum,mid+1,r);
    		}
    	}
    	#undef mid
    }
    
    namespace BIT{
    	int rt[N];
    	inline int lowbit(int x){return x&(-x);}
    	inline void update(int x,int v,int tp){
    		for(;x;x-=lowbit(x)) SGT::update(rt[x],v,tp);
    	}
    	inline ll query(int x,int v,int m){
    		int tot=0;
    		for(;x<=n;x+=lowbit(x)) q[++tot]=rt[x];
    		return SGT::query(tot,v,m);
    	}
    }
    
    multiset<int> s;
    int main(){
    //	freopen("1.in","r",stdin); 
    	read(n);read(m);
    	for(int i=1;i<=n;++i) read(a[i]),b[i]=a[i];
    	sort(b+1,b+n+1);
    	cnt=unique(b+1,b+n+1)-b-1;
    	for(int i=1;i<=n;++i) pos[i]=lower_bound(b+1,b+cnt+1,a[i])-b;
    	for(int i=1;i<=n;++i) BIT::update(i,pos[i],1),s.insert(a[i]);
    	while(s.size()){
    		vector<int> dec,rel;
    		int rec=m;ans++;
    		while(s.size()&&(*s.begin())<=rec){
    			dec.push_back(*s.begin());
    			rec-=dec.back();s.erase(s.begin());
    		}
    		int sum=dec.size();rec=m;
    		for(int i=1;i<=sum;++i){
    			int l=1,r=n-(sum-i+1)+1,ans=0;
    			while(l<=r){
    				int mid=(l+r)>>1;
    				if(BIT::query(mid,sum-i+1,rec)<=rec) l=mid+1,ans=mid;
    				else r=mid-1;
    			}
    			BIT::update(ans,pos[ans],-1);
    			rec-=a[ans];
    			rel.push_back(a[ans]);	
    		}
    		for(int v:dec) s.insert(v);
    		for(int v:rel) s.erase(s.find(v));
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    T2 watermelon

    Description

    给出一棵树,结点有点权,求所有树上简单路径的最长上升子序列的最大值,(nle 10^5,a_ile 10^9)

    Solution

    考虑 (DP),注意到一个简单路径可以被拆为向上的部分和向下的部分。所以设 (f_{u,i}) 表示 (u) 的子树中从 (u) 向下且第一项是 (i) 的 LIS 的最大长度,(g_{u,i}) 表示 (u) 的子树中 (u) 的某个子孙向上到 (u) 且最后一项是 (i) 的 LIS 的最大长度。

    (u) 到父亲 (fa) ,转移考虑将 (a_{fa}) 作为 (LIS) 的开头或结尾:

    [max_{i=a_{fa}+1}^{n}f_{u,i}+1 ightarrow f_{fa,a_{fa}}\ max_{i=1}^{a_{fa}-1}g_{u,i}+1 ightarrow g_{fa,a_{fa}}\ ]

    于是可以对每个节点用线段树维护 (f)(g),从 (u) 转移到 (fa) 只需要先将 (u) 的线段树用上面的转移方程进行修改,再直接合并到 (fa) 的线段树上即可。统计答案时,考虑在合并 (u) 之前(此时 (fa) 的线段树维护的线段树维护的是只考虑从前几个儿子上来的 LIS 时的 (f)(g) )更新答案,有 (f_{u,i}+max_{j<i} g_{fa,j} ightarrow ans)(g_{fa,i}+max_{j<i} g_{u,j} ightarrow ans)。实际实现时,同时从 (u)(fa) 的线段树向下走,每次用 $max f_{lson[fa]}+max g_{rson[u]} $ 更新答案即可,这是经典的线段树合并套路。

    Code

    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+10;
    
    struct node{
    	int v,nxt;
    }e[N<<1];
    int cnt,first[N],pos[N],tot,buc[N],ans;
    inline void add(int u,int v){e[++cnt].v=v;e[cnt].nxt=first[u];first[u]=cnt;}
    namespace SGT{
    	const int M=N<<7;
    	#define mid ((l+r)>>1)
    	int rtf[N],rtg[N];
    	int ls[M],rs[M],mx[M],del[M],top,sum;
    	inline int newnode(){return top?del[top--]:++sum;}
    	inline void dele(int p){
    		ls[p]=rs[p]=mx[p]=0;
    		del[++top]=p;
    	}
    	inline int getmx(int p,int ql,int qr,int l=1,int r=tot){
    		if(ql<=l&&r<=qr) return mx[p];
    		if(!p) return 0;
    		int ans=0;
    		if(ql<=mid) ans=max(ans,getmx(ls[p],ql,qr,l,mid));
    		if(qr>mid) ans=max(ans,getmx(rs[p],ql,qr,mid+1,r));
    		return ans;  
    	}
    	inline void update(int &p,int x,int v,int l=1,int r=tot){
    		if(!p) p=newnode();
    		if(l==r){mx[p]=max(mx[p],v);return ;}
    		if(x<=mid) update(ls[p],x,v,l,mid);
    		else update(rs[p],x,v,mid+1,r);
    		mx[p]=max(mx[ls[p]],mx[rs[p]]);
    	}
    	inline int merge(int p1,int p2,int l=1,int r=tot){
    		if(!p1||!p2) return p1+p2;
    		if(l==r){
    			mx[p1]=max(mx[p1],mx[p2]);
    			dele(p2);
    			return p1;
    		} 
    		mx[p1]=max(mx[p1],mx[p2]);
    		ls[p1]=merge(ls[p1],ls[p2],l,mid);
    		rs[p1]=merge(rs[p1],rs[p2],mid+1,r);
    		dele(p2);
    		return p1;
    	}
    	inline void query(int p1,int p2,int l=1,int r=tot){
    		if(!p1||!p2) return ;
    		if(l==r) return ;
    		ans=max(ans,mx[ls[p1]]+mx[rs[p2]]);
    		query(ls[p1],ls[p2],l,mid);
    		query(rs[p1],rs[p2],mid+1,r);
    	}
    	#undef mid
    }
    using namespace SGT;
    
    int n,a[N];
    
    inline void work(int u,int f){
    	bool flag=0;
    	for(int i=first[u];i;i=e[i].nxt){
    		int v=e[i].v;
    		if(v==f) continue;
    		work(v,u);
    		if(flag) query(rtg[v],rtf[u]);
    		if(flag) query(rtg[u],rtf[v]);
    		update(rtg[v],pos[u],getmx(rtg[v],1,pos[u]-1)+1);
    		update(rtf[v],pos[u],getmx(rtf[v],pos[u]+1,tot)+1);
    		rtf[u]=merge(rtf[u],rtf[v]);
    		rtg[u]=merge(rtg[u],rtg[v]);
    		flag=1;
    	}	
    	if(!flag) update(rtf[u],pos[u],1),update(rtg[u],pos[u],1);
    	ans=max(ans,mx[rtf[u]]);ans=max(ans,mx[rtg[u]]);
    }
    
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;++i) scanf("%d",&a[i]),buc[i]=a[i];
    	sort(buc+1,buc+n+1);
    	tot=unique(buc+1,buc+n+1)-buc-1;
    	for(int i=1;i<=n;++i) pos[i]=lower_bound(buc+1,buc+tot+1,a[i])-buc;
    	for(int i=1,u,v;i<n;++i) scanf("%d%d",&u,&v),add(u,v),add(v,u);
    	work(1,0);
    	printf("%d
    ",ans);
    	return 0;
    } 
    

    T3 emiya

    Description

    (n) 个人和 (m) 种菜 , 第 (i) 个人对第 (j) 道菜的喜爱程度为 (a_{i,j}), 如果 (a_{i,j}=−1)则表示不喜欢 .

    现在你要选择一个菜的集合,你会获得喜欢集合中所有菜的人对这些菜的喜爱程度之和的权值,最大化这个权值,(nle 20,mle 10^6,a_{i,j}le 10^9)

    Solution

    考虑求出 (f_S) 表示钦定 (S) 中的人喜欢所有的菜,不管其他人时,能获得的最大权值。显然答案 (=max_S f_S)

    考虑 (a_{i,j}) 能为哪些 (S) 作出贡献,设 (t_j) 为喜欢 (j) 的人的集合,那么会受到 (a_{i,j}) 贡献的 (S) 应当满足 (Sin t_j,iin S)。考虑记 (g_S) 表示对 (S) 的所有子集一起造成的贡献,即 (f_S=sum_{Sin T}g_{T})。于是贡献就相当于 (g_{t_j}+=a_{i,j},g_{t_jotimes 2^i}-=a_{i,j})

    求出 (g) 后再求 (f),直接 (FWT)(FMT) 求解即可。

    Code

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=(1<<20)+20; 
    int n,m,a[21][N],s[N];
    ll f[N];
    int main(){
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;++i) 
    		for(int j=1;j<=m;++j){
    			scanf("%d",&a[i][j]);
    			if(a[i][j]!=-1) s[j]|=1<<i-1;
    		}
    	for(int i=1;i<=n;++i) 
    		for(int j=1;j<=m;++j)
    			if(a[i][j]!=-1) f[s[j]]+=a[i][j],f[s[j]^(1<<i-1)]-=a[i][j];
    	for(int i=0;i<n;++i)
    		for(int j=0;j<(1<<n);++j) if(j&(1<<i)) f[j^(1<<i)]+=f[j];
    	ll ans=0;
    	for(int i=0;i<(1<<n);++i) ans=max(ans,f[i]);
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    T4 tree

    Description

    你需要实现两个函数 (encode,decode)

    (encode) 的功能是接收一个 (n) 个点的有根树,返回一个 (128) 位二进制数。

    (decode) 的功能是接收一个 (128) 位的二进制数,返回一个有根树。

    题目会给你 (T) 颗有根树给 (encode),把你 (decode) 返回的二进制数给第二个函数。

    你需要使得第二个函数返回的有根树和给你的有根树同构。

    同构指的是将每个节点的儿子以编号大小排序后,两棵树无标号有根同构。

    (nle 70,Tle 10^5)

    Solution

    两棵树同构,其实就相当于,(dfs) 遍历整个树时,每次从父亲走向儿子在序列后添一个 (0),从儿子走向父亲在序列后添一个 (1),两棵树得到的序列相同则这两棵树同构。因此直接传递这个长为 (2(n-1)) 的序列就可以完成 (n=65) 的情况。

    注意到这个 (01) 序列如果把 (0) 看作左括号,(1) 看作右括号,那么原序列就变成了一个合法的括号序列。而长为 (n) 的合法括号序列有 (C(n)) 个,其中 (C) 是卡特兰数,而 (C(128)<2^{128}),因此考虑求出每个序列是字典序第几小的合法括号序列,然后就能将每棵树映射为一个合法括号序列了。

    考虑求出 (f_{i,j}) 表示已经有了 (i) 个左括号,(j) 个右括号且前半部分保证合法,接下来有多少种放括号的方案使得括号序列合法。计算一个括号序列的字典序时,如果第 (i) 个位置是右括号,那么此前位置与序列相同,第 (i) 个位置是左括号的序列都比他小,这样的序列有 (f_{x+1,y}) 个,其中 (x,y) 表示前 (i-1) 个位置有 (x) 个左括号,(y) 个右括号。

    于是直接预处理出来每个括号长度对应的 (f),总复杂度为 (mathcal O(n^3+nT))

    Code

    #include "tree.h"
    #include<bits/stdc++.h>
    using namespace std; 
    #define u128 unsigned __int128
    const int N=75;
    u128 f[N][N][N];
    int vis[N];
    inline void init(int n){
    	f[n][n][n]=1;
    	for(int sum=n<<1;sum>=1;--sum){
    		for(int i=min(sum,n);i>=0&&i>=sum-i;--i){ 
    			int j=sum-i;
    			if(!f[n][i][j]) continue;
    			if(i-1>=j&&i) f[n][i-1][j]+=f[n][i][j];
    			if(j) f[n][i][j-1]+=f[n][i][j];
    		}
    	}
    }
    vector<int> to[N];
    int ans[N<<1],top;
    inline void dfs(int u,int f){
    	for(int v:to[u]){
    		if(v==f) continue;
    		ans[++top]=0;dfs(v,u);
    	}
    	if(f) ans[++top]=1;
    }
    inline void write(u128 M){
    	if(M>=10) write(M/10);
    	putchar(M%10+'0');
    }
    u128 encode(int n,const int *p){
    	if(!vis[n-1]) vis[n-1]=1,init(n-1);
    	for(int i=1;i<=n;++i) to[i].clear();
    	for(int i=2;i<=n;++i) to[p[i]].push_back(i);
    	top=0;dfs(1,0);
    	u128 ret=0;
    	for(int i=1,a=0,b=0;i<=top;++i){
    		if(ans[i]==1) ret+=f[n-1][a+1][b],b++;
    		else a++;
    	}
    	return ret;
    }
    int dfn[N<<1];
    void decode(int n,u128 M,int *p){
    	if(!vis[n-1]) vis[n-1]=1,init(n-1); 
    	top=0;p[1]=0;
    	for(int i=1,a=0,b=0;i<=(n-1)<<1;++i){
    		if(M>=f[n-1][a+1][b]) M-=f[n-1][a+1][b],dfn[i]=1,b++;
    		else dfn[i]=0,a++; 
    	}
    	int now=1,cnt=1;
    	for(int i=1;i<=(n-1)<<1;++i){
    		if(!dfn[i]){
    			++cnt;
    			p[cnt]=now;now=cnt;
    		}else now=p[now];
    	}
    }
    
  • 相关阅读:
    vue2.0 练习项目-外卖APP(2)
    vue2.0 练习项目-外卖APP(1)
    迈向全栈开发学习(3)
    迈向全栈开发学习(2)
    迈向全栈开发学习(1)
    前端到后台ThinkPHP开发整站(完)
    前端到后台ThinkPHP开发整站(7)
    前端到后台ThinkPHP开发整站(6)
    前端到后台ThinkPHP开发整站(5)
    uniapp 设置全局scss生效
  • 原文地址:https://www.cnblogs.com/tqxboomzero/p/14782301.html
Copyright © 2020-2023  润新知