• 【纪中集训】2019.08.13【省选组】模拟3


    T1

    Description

    • 给出一个(N*N(N≤600))的非负矩阵(B=(b_{ij})),和一个(1*N)的非负矩阵(C=(c_{ij}))(A=(a_{ij}))是一个(1*N)(01)矩阵,令矩阵(D=(d_{ij}) (A*B-C)*A^T),则(D)是一个(1*1)的矩阵。要求构造(A)矩阵最大化(D)矩阵的元素值,输出得到的(D)矩阵。

    Solution

    • 化下式子就知道答案为:(sum_{i=1}^n A_i[(sum_{j=1}^n A_jB_{ij})-C_i])
    • 这样的话,可以想象有一张(N)个点的完全图,第(i)个点的点权为(C_i),边(<i,j>)的权为(B_{ij})。那问题就转化为在这张图上找一个子图,使得子图内边权和减点权和最大。
    • 可以yy成一种神奇的二元关系,然后转化为最小割模型。这里提供一种建图:对于点(i),我们从源点(S)向它连一条容量为(sum_{j=1}^n B_{ij})的边,从它向汇点(T)连一条容量为(C_i)的边;然后对于一个点对(i≠j),从(i)(j)连一条容量为(B_{ij})的边。这样用(sum B)减去最小割即为答案。
    • 上述建图法的正确性是比较显然的。我们假设一开始全选,(ans=sum B);然后考虑选点(i)的代价是(C_i)(即让(ans-=C_i)),不选点(i)的代价是(sum_{j可不选} B_{ij}+sum_{j必选}B_{ji})。显然这种代价和即为答案。实际上,我们只要建出一个满足这些代价的图即可(因而有无数种建图方法)。

    Code

    #include <cstdio>
    #include <algorithm>
    #define min(x,y) (x<y?x:y)
    #define fo(i,a,b) for(int i=a;i<=b;i++)
    using namespace std;
    
    const int N=666,M=2*N*N,inf=0x7fffffff;
    int n,S,T,x,oub[N],la[N],ans,cur[N],dis[N],GAP[N];
    struct edge{int v,l,ne;}e[M];
    
    void link(int x,int y,int z)
    {
    	static int tot=1;
    	e[++tot]=(edge){y,z,la[x]}, la[x]=tot;
    	e[++tot]=(edge){x,0,la[y]}, la[y]=tot;
    }
    
    int flow(int x,int res)
    {
    	if(x==T) return res;
    	int i,have=0;
    	for(i=cur[x]; i; i=e[i].ne)
    		if(e[i].l&&dis[x]==dis[e[i].v]+1)
    		{
    			cur[x]=i;
    			int now=flow(e[i].v,min(res-have,e[i].l));
    			e[i].l-=now, e[i^1].l+=now;
    			if((have+=now)==res) return have;
    		}
    	cur[x]=la[x];
    	if(!--GAP[dis[x]]) dis[S]=T+1;
    	GAP[++dis[x]]++;
    	return have;
    }
    
    int main()
    {
    	scanf("%d",&n), T=n+1;
    	fo(i,1,n) fo(j,1,n)
    	{
    		scanf("%d",&x), oub[i]+=x, ans+=x;
    		if(i^j) link(i,j,x);
    	}
    	fo(i,1,n) link(S,i,oub[i]), scanf("%d",&x), link(i,T,x);
    	GAP[0]=T+1;
    	while(dis[S]<=T) ans-=flow(S,inf);
    	printf("%d",ans);
    }
    

    T2

    Description

    • 一个平面直角坐标系上,有(N(≤7000))个点,标号为1到N,其中第i个点的坐标为(x[i], y[i])。   
    • 求满足以下两个条件的点列{p[i]}的数目(假设{p[i]}的长度为M):   
    1. 对任意1 <= i < j <= M,必有y[p[i]] > y[p[j]];   
    2. 对任意3 <= i <= M,必有x[p[i-1]] < x[p[i]] < x[p[i-2]]或者x[p[i-2]] < x[p[i]] < x[p[i-1]]。   
    • 求满足条件的非空序列{p[i]}的数目,结果对一个整数(Q(in[1,1000000000]))取模。

    Solution

    • 一道灵光一闪后就是普及难度的题目。
    • 可以先将点按x坐标排序,然后DP。设f[i][0/1]表示考虑到第i个点,它的下一个点在它左边/右边的答案。那我们可以从小到大枚举i,再从i-1到1枚举一个j,比较y[i]和y[j],若y[i]<y[j]则用f[i][0]转移f[j][1],否则用f[j][1]转移f[i][0]。
    • 画个图就很明了了。由于我们一定是从y小的转移向y大的,所以是没有后效性的;而且由于我们按x坐标排了序,在用一个f转移前,已经考虑完了中间的(比如用f[i][0]转移f[j][1],我们已经考虑完了所有j<j'<i的f[j'][1]对f[i][0]的贡献了),因而也是正确的。
    • 时间复杂度(O(n^2))

    Code

    #include <cstdio>
    #include <algorithm>
    #define P(x,y) (x+=y)%=Q
    #define fo(i,a,b) for(int i=a;i<=b;i++)
    #define fd(i,a,b) for(int i=a;i>=b;i--)
    using namespace std;
    
    const int N=7010;
    int n,Q,f[N][2],ans;
    struct dot{int x,y;}p[N];
    
    bool cmp(const dot&a,const dot&b) {return a.x<b.x;}
    
    int main()
    {
    	scanf("%d%d",&n,&Q);
    	fo(i,1,n) scanf("%d%d",&p[i].x,&p[i].y);
    	sort(p+1,p+n+1,cmp);
    	fo(i,1,n)
    	{
    		f[i][0]=f[i][1]=1;
    		fd(j,i-1,1) p[j].y>p[i].y ? P(f[j][1],f[i][0]) : P(f[i][0],f[j][1]);
    	}
    	fo(i,1,n) P(ans,f[i][0]), P(ans,f[i][1]);
    	printf("%d",(ans-n+Q)%Q);
    }
    

    T3

    Description

    • 给出一棵(N(≤100000))个带权点的有根树,每个点的孩子是从左到右有序的。一棵树的价值为所有叶节点的权值和减去每对相邻叶节点的路径上(不含那两个叶节点)的最大点权值。剪枝的本质是将某些结点的子树全部删去,使自己作为新的叶结点。
    • 对树进行一些剪枝,使树的价值最大。剪枝的方式为:如果一个结点的孩子都是叶结点,就可以将它所有的孩子剪去。

    Solution

    • 也是一道水题。。。没切真是耻辱
    • 记lc(x)为从x出发,沿最左节点走下去形成的链,称为左链;rc(x)为右链。那每次肯定是有一对相邻的兄弟节点u、v,用一个rc(u)的答案更新lc(v)的答案。(当然反过来也可以,因为把整棵树左右翻转后的答案是不变的)
    • 暴力转移即可,因为一个点至多只会在一条左链和一条右链上,因此是(O(n))的。
    • 转移时用一个单调队列优化一下。

    Code

    #include <cstdio>
    #define max(x,y) (x>y?x:y)
    #define fo(i,a,b) for(int i=a;i<=b;i++)
    #define fd(i,a,b) for(int i=a;i>=b;i--)
    using namespace std;
    
    const int N=11e4,inf=0x3f3f3f3f;
    int n,w[N],t,p,pa[N],tot,to[N],ne[N],la[N],ls[N],rs[N],f[N],top,h[N],m[N],h1[N],h2[N],ans;
    
    void work(int x,int y)
    {
    	h1[0]=-inf;
    	for(top=0; x; x=rs[x])
    	{
    		h[++top]=x;
    		m[top+1]=max(m[top],w[x]);
    		h1[top]=max(h1[top-1],f[x]);
    		h2[top]=f[x]-m[top];
    	}
    	h2[top+1]=-inf;
    	fd(i,top,1) if(h2[i]<h2[i+1]) h2[i]=h2[i+1];
    	for(int k=0,d=m[1]; y; y=ls[y])
    	{
    		while(k<top&&d>m[k+1]) k++;
    		f[y]=w[y]+max(h1[k]-d,h2[k+1]);
    		if(d<w[y]) d=w[y];
    	}
    }
    
    void dfs(int x,bool l,bool r)
    {
    	int ly=0;
    	for(int i=la[x],y; y=to[i]; ly=y,i=ne[i]) 
    	{
    		if(ly) m[1]=w[x],work(ly,y);
    		dfs(y,l&&!ly,r&&rs[x]==y);
    	}
    	if(l&&f[x]<w[x]) f[x]=w[x];
    	if(r&&f[x]>ans) ans=f[x];
    }
    
    int main()
    {
    	scanf("%d",&n);
    	fo(i,1,n)
    	{
    		scanf("%d%d",&w[i],&t);
    		fo(j,1,t)
    		{
    			scanf("%d",&p);
    			to[++tot]=p, ne[tot]=la[i], la[pa[p]=i]=tot;
    			if(j==1) rs[i]=p;
    			if(j==t) ls[i]=p;
    		}
    		f[i]=-inf;
    	}
    	dfs(1,1,1);
    	printf("%d",ans);
    }
    
  • 相关阅读:
    SpringBoot结合ShardingSphere实现分库分表、读写分离
    SpringBoot结合ShardingSphere实现主从读写分离
    使用Sentinel实现Spring Cloud Gateway网关流量控制
    使用Sentinel实现热点参数限流
    对比学习UIKit和AppKit--入门级
    UIViewController
    C++的异常处理之一:throw是个一无是处的东西
    About Closure
    理解Objective C 中id
    关于文件压缩的一些小知识
  • 原文地址:https://www.cnblogs.com/Iking123/p/11347964.html
Copyright © 2020-2023  润新知