• 2019.03.09 ZJOI2019模拟赛 解题报告


    得分: (20+0+40=60)(T1)大暴力,(T2)分类讨论写挂,(T3)分类讨论(40)分)

    (T1):天空碎片

    一道神仙数学题,貌似需要两次使用中国剩余定理

    反正不会做。

    (T2):未来拼图

    通过题目描述,我们可以发现,这道题就是让你求出一个多项式,使其与自己循环卷积能够得到给定的式子。(类似于多项式开方

    则我们可以先考虑将这个式子(DFT)点值表示法,然后将每个数开方。

    由于每个数的平方根有两个,因此我们需要逐一枚举其正负性。

    又考虑到其第(1sim n-1)项是对称的,因此需枚举的项个数减半,效率也就大大提高。

    然后,对于每一个序列,我们将其(IDFT)回原来的式子,暴力验证其正确性。

    如果正确,则我们将其扔入(set)(因此这个序列需要用(vector)存储),这样一来可以去重,以来也方便求出字典序最小的序列。

    最后方案数就是(set)(size()),字典序最小的解就是(set)中的第一项。

    代码如下:

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 25
    #define DB double
    #define max(x,y) ((x)>(y)?(x):(y))
    #define min(x,y) ((x)<(y)?(x):(y))
    #define INF 1e9
    using namespace std;
    int n,a[N+5];
    class PolynomialSolver
    {
    	private:
    		typedef vector<int> V;V K;set<V> S;
    		struct Complex//定义复数
    		{
    			DB x,y;I Complex(Con DB& a=0,Con DB& b=0):x(a),y(b){}
    			I Complex operator - () const {return Complex(-x,-y);}//取负
    			I Complex operator + (Con Complex& t) const {return Complex(x+t.x,y+t.y);}//复数加法
    			I Complex operator - (Con Complex& t) const {return Complex(x-t.x,y-t.y);}//复数减法
    			I Complex operator * (Con Complex& t) const {return Complex(x*t.x-y*t.y,x*t.y+y*t.x);}//复数乘法
    		}s[N+5],w[N+5],BackUp[N+5],Copy[N+5];
    		I Complex Sqrt(Con Complex& x)//开平方(取任一平方根,因为之后会枚举正负性)
    		{
    			Reg DB Ang=atan2(x.y,x.x),Len=sqrt(x.x*x.x+x.y*x.y);//求出原先复数的辐角与模长
    			return Ang/=2,Len=sqrt(Len),Complex(Len*cos(Ang),Len*sin(Ang));//辐角除以2,模长开平方,求出开平方后的复数
    		}
    		I void BruteForceFT(Complex* s)//DFT与IDFT
    		{
    			RI i,j;for(i=0;i^n;++i) BackUp[i]=s[i];//先复制一份
    			for(i=0;i^n;++i) for(s[i]=Complex(),j=0;j^n;++j) s[i]=s[i]+BackUp[j]*w[i*j%n];//进行变换
    		}
    		I bool Check(V k,int* tar)//验证一个解的合法性
    		{
    			RI i,j,t;for(i=1;i^n;++i) if(k[i]^k[n-i]) return false;//如果不满足对称性,返回false
    			for(i=0;i^n;++i)//枚举第i个位置
    			{
    				for(t=j=0;j^n;++j) t+=k[j]*k[(i-j+n)%n];//枚举第j个位置给第i个位置的贡献
    				if(t^tar[i]) return false;//如果第i个位置算出的值与给定序列中第i位上的值不相等,则返回false
    			}return true;//返回true
    		}
    	public:
    		I void Solve(int* v)//求解答案
    		{
    			RI i,j,p=n+2>>1,lim=1<<p,tot=0;Reg DB theta=2*acos(-1)/n;
    			for(i=0;i^n;++i) w[i]=Complex(cos(theta*i),sin(theta*i));//预处理出单位根,用于做DFT
    			for(i=0;i^n;++i) s[i]=v[i];for(BruteForceFT(s),i=0;i^n;++i) s[i]=Sqrt(s[i]);//DFT一遍,然后将每个数开方
    			for(i=0;i^n;++i) w[i].y=-w[i].y;//将单位根的虚部取负,用于做IDFT
    			for(S.clear(),i=0;i^lim;++i)//枚举根的正负性
    			{
    				for(j=0;j^n;++j) Copy[j]=(i>>min(j,n-j))&1?-s[j]:s[j];//复制一份
    				for(BruteForceFT(Copy),K.clear(),j=0;j^n;++j) K.push_back(max(Copy[j].x/n,0)+0.5);//IDFT一遍,求出数列
    				Check(K,a)&&(S.insert(K),0);//验证其正确性,如果正确则扔入set
    			}if(S.empty()) return (void)(puts("0"));printf("%d ",S.size());//如果为空,输出0表示无解,否则输出set的大小表示方案数
    			for(K=*S.begin(),i=0;i^n;++i) printf("%d ",K[i]);putchar('
    ');//输出set中的第一项,即字典序最小的答案
    		}
    }P; 
    int main()
    {
    	freopen("puzzle.in","r",stdin),freopen("puzzle.out","w",stdout);
    	RI Ttot,i;scanf("%d",&Ttot);W(Ttot--)
    	{
    		for(scanf("%d",&n),i=0;i^n;++i) scanf("%d",&a[i]);//读入
    		P.Solve(a);//求解
    	}return 0;
    }
    

    (T3):完美理论

    我们可以考虑枚举一个点,作为两棵树共同的根节点。

    然后,我们强制若选择一个节点,就必须选择其父节点,这样就可以保证连通性了。

    于是这就成为了一个经典的最大权闭合图的模板,可以用网络流做。

    我们从每个节点向其在两棵树中的父节点分别连一条边权为(INF)的边,然后从源向权值为正的点连一条容量为点权的边,并从权值为负的点向汇连一条容量为点权相反数的边。

    再求出正点权总和减去最大流的值,就是以该点为根时的最优答案了。

    最后把以每个点为根的答案取个(max),就是最终答案了。

    代码如下:

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100
    #define min(x,y) ((x)<(y)?(x):(y))
    #define Gmax(x,y) (x<(y)&&(x=(y)))
    #define INF 1e9
    using namespace std;
    int n,a[N+5];
    class Tree//存储一棵树
    {
    	private:
    		int ee;
    		I void dfs(CI x) {for(RI i=lnk[x];i;i=e[i].nxt) e[i].to^fa[x]&&(fa[e[i].to]=x,dfs(e[i].to),0);}//遍历树
    	public:
    		int fa[N+5],lnk[N+5];struct edge {int to,nxt;}e[N<<1];
    		I void add(CI x,CI y) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y;}//建边
    		I void Clear() {ee=0,memset(lnk,0,sizeof(lnk));}//清空
    		I void MR(CI x) {fa[x]=0,dfs(x);}//将点x作为根,现将其父节点赋为0,然后dfs
    }T1,T2;
    class Dinic//Dinic跑网络流
    {
    	private:
    		#define add(x,y,v) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].Cap=v)
    		static const int Psz=N+2,Lsz=6*N;int ee,lnk[Psz+5],cur[Psz+5],q[Psz+5],dep[Psz+5];
    		struct edge {int to,nxt,Cap;}e[Lsz+5];
    		I bool BFS()//BFS找增广路
    		{
    			RI i,k,H=1,T=1;memset(dep,0,sizeof(dep)),dep[q[1]=s]=1;W(H<=T&&!dep[t])
    				for(i=lnk[k=q[H++]];i;i=e[i].nxt) e[i].Cap&&!dep[e[i].to]&&(dep[q[++T]=e[i].to]=dep[k]+1);
    			return dep[t]?(memcpy(cur,lnk,sizeof(lnk)),true):false;
    		}
    		I int DFS(CI x,RI f)//DFS统计流量
    		{
    			if(!(x^t)||!f) return f;RI i,t,res=0;
    			for(i=cur[x];i;i=e[i].nxt)
    			{
    				if(cur[x]=i,(dep[x]+1)^dep[e[i].to]||!(t=DFS(e[i].to,min(f,e[i].Cap)))) continue;
    				if(e[i].Cap-=t,e[((i-1)^1)+1].Cap+=t,res+=t,!(f-=t)) break;
    			}return !res&&(dep[x]=-1),res;
    		}
    	public:
    		int s,t;I Dinic() {s=1,t=2;}I int P(CI x) {return x+2;}
    		I void Clear() {ee=0,memset(lnk,0,sizeof(lnk));}//清空数组
    		I void Add(CI x,CI y,CI v) {add(x,y,v),add(y,x,0);}//建边
    		I int MaxFlow() {RI res=0;W(BFS()) res+=DFS(s,INF);return res;}//求最大流
    		#undef add
    }D;
    int main()
    {
    	freopen("theory.in","r",stdin),freopen("theory.out","w",stdout);
    	RI Ttot,i,j,x,y,sum,ans,t;scanf("%d",&Ttot);W(Ttot--)
    	{
    		for(ans=sum=0,scanf("%d",&n),T1.Clear(),T2.Clear(),i=1;i<=n;++i) scanf("%d",&a[i]),a[i]>0&&(sum+=a[i]);//读入数据,统计正点权总和
    		for(i=1;i^n;++i) scanf("%d%d",&x,&y),T1.add(x,y),T1.add(y,x);//建边
    		for(i=1;i^n;++i) scanf("%d%d",&x,&y),T2.add(x,y),T2.add(y,x);//建边
    		for(i=1;i<=n;++i)
    		{
    			for(T1.MR(i),T2.MR(i),D.Clear(),j=1;j<=n;++j) a[j]>0?D.Add(D.s,D.P(j),a[j]):D.Add(D.P(j),D.t,-a[j]);//以i为根,从源向权值为正的点连边,并从权值为负的点向汇连边
    			for(j=1;j<=n;++j) T1.fa[j]&&(D.Add(D.P(j),D.P(T1.fa[j]),INF),0),T2.fa[j]&&(D.Add(D.P(j),D.P(T2.fa[j]),INF),0);//向父节点连边
    			t=D.MaxFlow(),Gmax(ans,sum-t);//更新答案
    		}printf("%d
    ",ans);//输出答案
    	}return 0;
    }
    
  • 相关阅读:
    POJ
    POJ
    HDU-3374 String Problem (最小最大表示法)
    HDU-2328 Corporate Identity (暴力)
    HDU-1238 Substrings (kmp)
    kmp处理题型总结
    Numpy用户指南
    Docker 容器连接
    Docker 镜像使用
    docker容器的使用
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/Contest20190309.html
Copyright © 2020-2023  润新知