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;
}
待到再迷茫时回头望,所有脚印会发出光芒