把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

2019.03.09 ZJOI2019模拟赛 解题报告

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

\(T1\):天空碎片

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

反正不会做。

\(T2\):未来拼图

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

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

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

又考虑到其第\(1\sim 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('\n');//输出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\n",ans);//输出答案
	}return 0;
}
posted @ 2019-03-10 17:43  TheLostWeak  阅读(187)  评论(0编辑  收藏  举报