市选总结
By SemiWaker
一头雾水
总述
感觉题目挺奇怪的,很多信息不明确。
做题时还是不够冷静,缺少冷静分析。
题解
二进制编号
题意
求第n个有k个1的01序列。
n≤107
k≤10
做题过程
水题
题解
最小的符合条件的序列显然是k个1。
然后多一个0,位置有k种。
再多一个0,这两个0可选的位置有k+1种。
依次类推。
长度为k+p时,有
Cpk+p−1
种序列。
那么我们就可以暴力求长度。
这题卡空间,不能直接杨辉三角,这个组合数每次上下都+1,有公式可以直接算
Ck+1n+1=Cknn+1k+1
暴力跑一下,发现k=2的时候只有4500位左右。
k=1显然有n+1位,特判即可。
然后从最高位开始考虑。
设当前是第i位,还有j个k。
如果当前位放0,那么剩下的方案数:
Cjp+k−i
。
如果n比它大,那么要放1,然后n减去该方案数即可。否则放0。
程序
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
#define MSET(x,y) memset((x),(y),sizeof(x))
#define Mmin(x,y) (((x)<(y))?(x):(y))
#define Mmax(x,y) (((x)>(y))?(x):(y))
typedef long long LL;
const int INF=(1<<29);
int n,k;
LL C[5100][11];
int main()
{
freopen("number.in","r",stdin);
freopen("number.out","w",stdout);
scanf("%d%d",&n,&k);
if (k==1)
{
putchar('1');
for (int i=1;i<=n-1;++i) putchar('0');
printf("\n");
return 0;
}
C[0][0]=1;
for (int i=1;i<=5000;++i)
{
C[i][0]=1;
for (int j=1;j<=min(i,10);++j) C[i][j]=C[i-1][j-1]+C[i-1][j];
}
int p=0;
for (LL t=1;n>t;p++) {n-=t;t=t*(p+k)/(1+p);}
putchar('1');
for (int i=2,j=k-1;i<=k+p;++i)
{
if (n>C[k+p-i][j]) {putchar('1');n-=C[k+p-i][j];j--;}
else putchar('0');
}
printf("\n");
return 0;
}
二叉搜索树
题意
给个二叉树,问最少改变多少点权才能变成二叉搜索树。
做题过程
这是一道不允许选手A的题目。
本来我按照整数做了,考到一半通知改为小数,然后数据又按整数做了。
表示无语
题解
显然,所有二叉搜索树题第一步都是变成序列。
然后题目变为:给个整数序列,求改变最少的数使得序列单调上升。
只能改为整数。
考虑留下最多的数。
假如有两个数Ai和Aj,当
Ai−Aj−1≥i−j−1
时,这两个就可以同时保留。
然后变成对
Ai−i
做最长不下降子序列。
我一开始犯傻直接把A离散化了……
程序
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
#define MSET(x,y) memset((x),(y),sizeof(x))
#define Mmin(x,y) (((x)<(y))?(x):(y))
#define Mmax(x,y) (((x)>(y))?(x):(y))
typedef long long LL;
const int INF=1000100001;
const int MAXN=200100;
int A[MAXN];
int A1[MAXN];
int f[MAXN];
int Son[MAXN][2];
int Cnt[MAXN];
int Stack[MAXN],STop,DFSClk;
void DFS();
int que[MAXN],qtail;
int n,m;
int ReadInt();
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;++i) A[i]=ReadInt();
for (int i=2;i<=n;++i)
{
int x,y;
x=ReadInt();y=ReadInt();
Son[x][y]=i;
}
DFS();
memcpy(A,A1,sizeof(A1));
que[0]=-INF;
qtail=1;
for (int i=1;i<=n;++i)
{
int p=upper_bound(que+0,que+qtail,A[i]-i)-que;
que[p]=A[i]-i;
f[i]=p;
if (p==qtail) qtail++;
}
int ans=n-1;
for (int i=1;i<=n;++i) ans=min(ans,n-f[i]);
printf("%d\n",ans);
return 0;
}
void DFS()
{
Stack[0]=1;STop=1;
DFSClk=0;
while (STop>0)
{
int x=Stack[STop-1];
Cnt[x]++;
if (Cnt[x]==1)
{
if (Son[x][0]==0) continue;
Stack[STop++]=Son[x][0];
} else
if (Cnt[x]==2)
{
A1[++DFSClk]=A[x];STop--;
if (!Son[x][1]) continue;
Stack[STop++]=Son[x][1];
}
}
}
int ReadInt()
{
int num=0,sgn=1;char c=getchar();
while (c!='-' && (c<'0' || c>'9')) c=getchar();
if (c=='-') sgn=-1,c=getchar();
while (c>='0' && c<='9') num=(num<<3)+(num<<1)+c-'0',c=getchar();
return num*sgn;
}
混乱的数列
题意
给一个数列,定义混乱度为:连续相同数块的数目。
如: 25 25 25 32 32 26 26 25 25
有4个块 (25*3) (32*2) (28*2) (25*2)
现在可以拿出k个数,再放回去。问操作后最小混乱度。
n k 100
每个数为 25~32
做题过程
感觉像个背包:每一块要整体拿。可是拿走之后周围的价值会变,弃。
然后想暴力DP每一位放什么,可是这样没法统计拿出了几个数,弃。
最后写个20分。
题解
放回去其实是个坑。
考虑把所有要拿出来的都一起拿出来(显然不会拿出来再放回去再拿出来),
然后再考虑一起放回去。
如果拿出了一个Ai,剩下至少一个Ai,那么放到剩下的旁边就什么都不会发生。
如果没剩下Ai,那么+1。
换句话说,我在拿的过程中不用管放回去,只要记一下拿了什么,哪些没剩下就好。
当然,这和爆搜没区别。
然后考虑优化一下,注意到有贡献的只有:拿了且没剩下的。换句话说:原来有,现在没的。
那么我只需要记每一种数有没有剩下即可,
28
状压即可。
然后就是个 O(nk28) 的DP了。
然而我一看这题是个NPC,以为是爆搜题直接没想。
程序
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
#define MSET(x,y) memset((x),(y),sizeof(x))
#define Mmin(x,y) (((x)<(y))?(x):(y))
#define Mmax(x,y) (((x)>(y))?(x):(y))
typedef long long LL;
const int INF=(1<<29);
int n,K;
int A[110];
int f[110][110][260][9];
int Val[260];
bool Have[10];
void Insert(int &f,int x)
{
if (f==-1) f=x;
else f=min(f,x);
}
int main()
{
freopen("mess.in","r",stdin);
freopen("mess.out","w",stdout);
scanf("%d%d",&n,&K);
for (int i=1;i<=n;++i) scanf("%d",&A[i]),A[i]-=25,Have[A[i]]=1;
memset(f,-1,sizeof(f));
f[1][K][0][8]=0;
for (int i=1;i<=n;++i)
for (int j=0;j<=n;++j)
for (int S=0;S<256;++S)
for (int k=0;k<9;++k)
if (f[i][j][S][k]!=-1)
{
Insert(f[i+1][j][S|(1<<A[i])][A[i]],
f[i][j][S][k]+int(k!=A[i]));
Insert(f[i+1][j-1][S][k],f[i][j][S][k]);
}
for (int S=0;S<256;++S)
{
for (int i=0;i<8;++i) if (((S>>i)&1)==0 && Have[i]) Val[S]++;
}
int ans=n;
for (int j=0;j<=K;++j)
for (int S=0;S<256;++S)
for (int k=0;k<9;++k)
if (f[n+1][j][S][k]!=-1)
ans=min(ans,f[n+1][j][S][k]+Val[S]);
printf("%d\n",ans);
return 0;
}
折线图
题意
给出一条折线,问每一条线段往后射出的射线会落到哪一条线段上?
(原文:某条线段能看到的第一条线段)
n 100000
坑点:题目没说给出的点x有序,而且上端点不算,只算下端点
做题过程
首先,随便画了几个例子,显然不能只保留凸包。
然后,发现显然只能看到斜率比当前大的。
没发现什么别的性质。
试着竖着建线段树,发现纯粹搞笑。
然后弃疗打暴力。
题解
考虑一条线段A-B。
假设我们已经维护好了当前线段之后的信息。
然后开始二分。
A-B射线如果会和左半部分相交,那么到左半区间,否则到右半区间。
怎样才会相交呢?
注意到我们不需要知道和哪一条相交,我们只需要知道会不会相交即可。
那么我们可以维护一个上凸壳,每次在凸壳上找一个点P,使得它在A-B的“上方”。换句话说,
SΔPAB>0
。这里的面积是叉积的有向面积。
怎么找这个点呢?我们只要找面积的极值即可。既然是个凸包了,那么三分即可。
这里就是它想法最核心的部分:既然单独哪一个求不出,那么我们不管是哪一个。
当然,不能每次建凸包,我们用个线段树来维护它即可。(其实类似划分树)
然而Naive的我为什么要去想竖着建线段树呢?
程序
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAXN=101000;
typedef long long LL;
struct Point
{
int x,y;
Point(int x1=0,int y1=0):x(x1),y(y1){}
Point operator-(const Point&B)const{return Point(x-B.x,y-B.y);}
} Ps[MAXN];
typedef Point Vector;
#define Cross(A,B) (LL((A).x)*((B).y)-LL((A).y)*((B).x))
int SpaceCnt;
Point que[MAXN*42];
int qh[MAXN*4],qt[MAXN*4];
int Ans[MAXN];
void Build(int root,int lef,int rig);
bool Check(int root,Point A,Point B);
int Query(int root,int lef,int rig,int id);
int n;
int ReadInt();
void OutInt(int x);
int OutTmp[100];
int main()
{
freopen("gay.in","r",stdin);
freopen("gay.out","w",stdout);
int T=ReadInt();
while (T--)
{
n=ReadInt();
SpaceCnt=0;
for (int i=1;i<=n;++i) Ps[i].x=ReadInt(),Ps[i].y=ReadInt();
Build(1,1,n-1);
for (int i=n-1;i>=1;--i) Ans[i]=Query(1,1,n-1,i);
for (int i=1;i<=n-1;++i) OutInt(Ans[i]),putchar(' ');
printf("\n");
}
return 0;
}
void Build(int root,int lef,int rig)
{
qh[root]=qt[root]=SpaceCnt;SpaceCnt+=(rig-lef+2);
for (int i=rig+1;i>=lef;--i)
{
while (qh[root]+1<qt[root] &&
Cross(Ps[i]-que[qt[root]-1],
que[qt[root]-1]-que[qt[root]-2])>=0)
qt[root]--;
que[qt[root]++]=Ps[i];
}
if (lef==rig) return;
int mid=(lef+rig)>>1;
Build(root<<1,lef,mid);
Build(root<<1|1,mid+1,rig);
}
bool Check(int root,Point A,Point B)
{
if (qh[root]==qt[root]) return 0;
int lef=qh[root],rig=qt[root]-1;
while (lef<rig)
{
int mid=(lef+rig)>>1;
if (Cross(B-A,que[mid]-A)<Cross(B-A,que[mid+1]-A)) lef=mid+1;
else rig=mid;
}
return Cross(B-A,que[lef]-A)>0;
}
int Query(int root,int lef,int rig,int id)
{
if (id<=lef)
if (!Check(root,Ps[id],Ps[id+1])) return 0;
if (lef==rig) return lef;
int mid=(lef+rig)>>1;
if (id<=mid)
{
int t=Query(root<<1,lef,mid,id);
if (t) return t;
}
return Query(root<<1|1,mid+1,rig,id);
return 0;
}
int ReadInt()
{
int num=0,sgn=1;char c=getchar();
while (c!='-' && (c<'0' || c>'9')) c=getchar();
if (c=='-') sgn=-1,c=getchar();
while (c>='0' && c<='9') num=(num<<3)+(num<<1)+c-'0',c=getchar();
return num*sgn;
}
void OutInt(int x)
{
if (x==0) {putchar('0');return;}
int l=0;for (;x>0;l++,x/=10) OutTmp[l]=x%10;
while (l) putchar(OutTmp[--l]+'0');
}
交换木板
题意
给出一个 n*n 01 矩阵,每次可以交换相邻行,求最小交换次数变为下三角矩阵。
n 40
做题过程
一开始直接打了个逆序对,发现显然错。
删掉写个费用流,写到一半发现不对劲。
然后开始写
O(n4)
的奇怪贪心:从下往上,每次往上找到一个必须放到当前位置的,再往下找到一个可以放到i-1位置的,一路交换过来。
最后拿了个80分。
题解
从上往下,每次找第一个能放到当前位置的,一路交换过来。
题解的思路是从位置限制最大去考虑的,我是从木板限制最大去考虑的。
然而显然我的比较Naive
程序
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
#define MSET(x,y) memset((x),(y),sizeof(x))
#define Mmin(x,y) (((x)<(y))?(x):(y))
#define Mmax(x,y) (((x)>(y))?(x):(y))
typedef long long LL;
const int INF=(1<<29);
int A[50];
int B[50];
int n,N,S,T;
char ST[50];
const int MAXN=10100;
int main()
{
freopen("board.in","r",stdin);
freopen("board.out","w",stdout);
scanf("%d",&n);
N=2*n+2;
S=2*n+1;
T=S+1;
for (int i=1;i<=n;++i)
{
scanf("%s",ST+1);
A[i]=0;
for (int j=n;j>=1;--j)
if (ST[j]=='1') {A[i]=j;break;}
}
int ans=0;
for (int i=1;i<=n;++i)
{
int j=i;
for (;A[j]>i;++j);
for (;j>i;--j) swap(A[j],A[j-1]),ans++;
}
printf("%d\n",ans);
return 0;
}
最优策略
题意
有 n 个格子,用小写字母来表示。
有一个棋子。
对于每一个给定的起点和终点,进行如下过程:
每次 A 在棋子所在格子列出的可选集合的集合里,选出一个可选集合。
然后 B 在 A 选出的可选集合里选一个格子,把棋子移动过去。
当棋子移动到终点时游戏结束。
A的目标是使游戏尽快结束,B则相反。
问每一对起点和终点的游戏时间。(无法结束则输-1。)
n 26
m 1000000
做题过程
这题的出题人的翻译水平……
原题:B每次在可选集合里选择一个不为当前格子的格子
黑体不是我加的,是原题的。
而且样例跟着错。(难道是出题人自己理解错了?)
想不看错题都不行啊
题解
暴力迭代。
图是建不出来了,Dijkstra是不用想的,所以只能暴力Bellman_Ford。
设能到达终点的格子的集合为S,如果某一个点的某一个可选集合是S的子集,那么当前点可以到达终点,步数为迭代次数。
时间复杂度玄学。
程序
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <set>
#include <queue>
using namespace std;
#define MSET(x,y) memset(x,y,sizeof(x))
#define Mmin(x,y) (((x)<(y))?(x):(y))
#define Mmax(x,y) (((x)>(y))?(x):(y))
typedef long long LL;
const int INF=(1<<29);
char ST[30];
int n;
int f[30];
struct Edge
{
int S;
Edge *nex;
Edge(){}
Edge(int S1,Edge*n1):S(S1),nex(n1){}
} ESpace[1001000],*E[30];
int ESpaceCnt;
void Insert(int x,int S)
{
Edge *P=&ESpace[++ESpaceCnt];
*P=Edge(S,E[x]);
E[x]=P;
}
bool vis[30];
int Ans[30][30];
void Work(int T)
{
MSET(vis,0);
for (int i=1;i<=n;++i) f[i]=100;
vis[T]=1;f[T]=0;
int Cur=(1<<T-1);
for (int p=1;p<=n;++p)
{
bool flag=0;
for (int i=1;i<=n;++i) if (!vis[i])
{
for (Edge *j=E[i];j!=NULL;j=j->nex)
{
int S=j->S;
if ((S&Cur)==S)
{
flag=1;
vis[i]=1;
f[i]=p;
break;
}
}
}
for (int i=1;i<=n;++i) if (vis[i]) Cur|=(1<<i-1);
if (!flag) break;
}
for (int i=1;i<=n;++i)
if (i==T) Ans[i][T]=0; else
if (!vis[i]) Ans[i][T]=-1;
else Ans[i][T]=f[i];
}
int main()
{
freopen("strategy.in","r",stdin);
freopen("strategy.out","w",stdout);
while (~scanf("%d",&n))
{
MSET(E,0);ESpaceCnt=0;MSET(Ans,0);
for (int i=1;i<=n;++i)
{
int x;
scanf("%d",&x);
while (x--)
{
scanf("%s",ST);
int len=strlen(ST);
int S1=0;
for (int j=0;j!=len;++j)
S1|=(1<<(ST[j]-'a'));
Insert(i,S1);
}
}
for (int i=1;i<=n;++i)
Work(i);
for (int i=1;i<=n;++i)
{
for (int j=1;j<=n;++j) printf("%d ",Ans[i][j]);
printf("\n");
}
}
return 0;
}
失窃的珍宝
题意
在 a-z 共26个珍宝中,有 3 个失窃了。
每一个珍宝本来归第一个或者第二个管理员管理。
现在有一些信息:某个管理员手中, X 和 Y 两件珍宝一共剩下 0/1/2 个。
问失窃方案数。
n 1000
做题过程
考前我才说不会考2-SAT……还好我会。
脸痛
题解
把各种情况列一列。
设每一件珍宝归第一个人管为0,否则为1。
按照剩下的数量分类:
0 :
对于每一件没有失窃的,显然归另一个人管。
1 :
两件都失窃无解。
只失窃一件时,没有失窃的那件归当前的人管。
两件都不失窃,只有一件归当前管,有两种可能。
2 :
显然两件都没失窃,都归当前的人管。
注意到有一种情况是有两种可能的,用2-SAT判一下是否可行就好。
O(n2) 的2-SAT一点问题都没有
程序
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
#define MSET(x,y) memset((x),(y),sizeof(x))
#define Mmin(x,y) (((x)<(y))?(x):(y))
#define Mmax(x,y) (((x)>(y))?(x):(y))
typedef long long LL;
const int INF=(1<<29);
int n;
int X[100],Y[100],Cur[100],Cnt[100];
int B[100],S[100];
char ST[100];
vector<int> E[100];
bool Mark[100];
int c[100],cn;
bool DFS(int x)
{
if (Mark[x^1]) return 0;
if (Mark[x]) return 1;
Mark[x]=1;
c[cn++]=x;
for (int i=0;i!=E[x].size();++i)
if (!DFS(E[x][i])) return 0;
return 1;
}
bool TwoSat()
{
memset(Mark,0,sizeof(Mark));
for (int i=0;i!=26;++i)
if (!Mark[i<<1] && !Mark[i<<1|1])
{
if (!DFS(i<<1))
{
while (cn) Mark[c[--cn]]=0;
if (!DFS(i<<1|1)) return 0;
}
}
return 1;
}
bool Put(int x,int y)
{
if (B[x]!=-1 && B[x]!=y) return 0;
B[x]=y;
E[x<<1|(y^1)].push_back(x<<1|y);
return 1;
}
bool Check()
{
MSET(B,-1);
for (int i=0;i!=26;++i) E[i<<1].clear(),E[i<<1|1].clear();
for (int i=1;i<=n;++i)
{
int x=X[i],y=Y[i];
if (Cnt[i]==0)
{
if (S[x]==0) if (!Put(x,Cur[i]^1)) return 0;
if (S[y]==0) if (!Put(y,Cur[i]^1)) return 0;
} else
if (Cnt[i]==1)
{
if (S[y]==1 && S[x]==1) return 0;
if (S[x]==1) if (!Put(y,Cur[i])) return 0;
if (S[y]==1) if (!Put(x,Cur[i])) return 0;
if (S[x]==0 && S[y]==0)
{
E[x<<1].push_back(y<<1|1);
E[y<<1].push_back(x<<1|1);
E[x<<1|1].push_back(y<<1);
E[y<<1|1].push_back(x<<1);
}
} else
{
if (S[x]==1 || S[y]==1) return 0;
if (!Put(x,Cur[i])) return 0;
if (!Put(y,Cur[i])) return 0;
}
}
return TwoSat();
}
int main()
{
freopen("stolen.in","r",stdin);
freopen("stolen.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;++i)
{
scanf("%s%d%d",ST,&Cur[i],&Cnt[i]);
Cur[i]--;
X[i]=ST[0]-'A';
Y[i]=ST[1]-'A';
}
int ans=0;
for (int i=0;i<26;++i)
{
S[i]=1;
for (int j=i+1;j<26;++j)
{
S[j]=1;
for (int k=j+1;k<26;++k)
{
S[k]=1;
if (Check()) ans++;
S[k]=0;
}
S[j]=0;
}
S[i]=0;
}
printf("%d\n",ans);
return 0;
}
不共边路径
题意
给一棵树,给一些路径,要求选择一些路径,使得任意路径不共边。
n 1000
D(x) 10
做题过程
当成点做了……
敲完树链剖分发现是边,弃。
题解
标程做法:
贪心。
考虑通过点x的路径。假设我们已经知道了x的子树信息。
有3种:
1、x为端点,另一端在x的子树里。
2、x不为端点,但是为LCA,两端都在x的子树里。
3、一端在x的子树里或是x,另外一端不在x的子树里。
对于第三种,我们扔给x的祖先去考虑。
所以我们保持每一棵子树连向父亲的边没有被覆盖,然后给每一棵子树维护一个序列,表示能作为起点往上找路径的点。
然后考虑第一和第二种。
注意到x连到每一棵子树只能选一次,换句话说,每一棵子树只能被选一次。
显然第一种完虐第二种,所以先选第一种。
选完之后,通过子树的序列暴力两两枚举,求出哪些子树之间有路径。
然后变成一个10个点图求最大匹配。
210
暴力DP即可。
剩下没有选的点,我们把它们的序列加到x的序列里,再把x自己加进去,扔给父亲节点处理即可。
我YY的做法:
类似点的做法,我们按边做DP。
考虑用一条边来表示一棵子树。
我们按照类似边分治的做法建用边代表一棵子树的树,每次选了一条路径后,用树链剖分求出剩下可选的子树的和。
然后DP。
表示写不出,但是显然优美多了
程序
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
const int MAXN=1100;
#define MSET(x,y) memset(x,y,sizeof(x))
int Up[MAXN][MAXN],UpCnt[MAXN];
bool Have[MAXN][MAXN];
int Son[MAXN],f[MAXN];
bool Link[20][20];
int g[MAXN];
int Fir[MAXN];
vector<int> E[MAXN];
int n;
void GetF(int x,int fa)
{
f[x]=0;
for (int i=0;i!=E[x].size();++i)
{
int y=E[x][i];
if (y==fa) continue;
GetF(y,x);
f[x]+=f[y];
}
int m=0;
for (int i=0;i!=E[x].size();++i)
if (E[x][i]!=fa) Son[m++]=E[x][i];
for (int i=0;i!=m;++i)
{
int y=Son[i];
for (int j=0;j!=UpCnt[y];++j)
if (Have[Up[y][j]][x])
{
f[x]++;
UpCnt[y]=0;
break;
}
}
MSET(Link,0);
for (int i=0;i<m;++i)
{
int y=Son[i];
for (int j=i+1;j<m;++j)
{
int z=Son[j];
bool flag=0;
for (int a=0;a<UpCnt[y];++a)
if (!flag)
for (int b=0;b<UpCnt[z];++b)
{
if (Have[Up[y][a]][Up[z][b]])
{
flag=1;
Link[i][j]=Link[j][i]=1;
break;
}
}
else break;
}
}
MSET(g,0);
int k=(1<<m)-1;
for (int S=1;S<=k;++S)
{
int i=Fir[S];
g[S]=g[S^(1<<i)];
for (int j=0;j<m;++j)
if (Link[i][j] && ((S>>j)&1))
g[S]=max(g[S],g[S^(1<<i)^(1<<j)]+1);
}
f[x]+=g[k];Up[x][0]=x;UpCnt[x]=1;
for (int i=0;i!=m;++i)
if (g[k^(1<<i)]==g[k])
{
int y=Son[i];
for (int j=0;j!=UpCnt[y];++j) Up[x][UpCnt[x]++]=Up[y][j];
}
}
int main()
{
freopen("path.in","r",stdin);
freopen("path.out","w",stdout);
for (int S=1;S<1024;++S)
for (int i=0;i<10;++i) if ((S>>i)&1) {Fir[S]=i;break;}
int T;
scanf("%d",&T);
while (T--)
{
scanf("%d",&n);
for (int i=1;i<=n;++i) E[i].clear();
MSET(f,0);MSET(Have,0);MSET(Up,0);MSET(UpCnt,0);
for (int i=1;i<n;++i)
{
int x,y;
scanf("%d%d",&x,&y);
E[x].push_back(y);
E[y].push_back(x);
}
int m;
scanf("%d",&m);
while (m--)
{
int x,y;
scanf("%d%d",&x,&y);
Have[x][y]=Have[y][x]=1;
}
GetF(1,0);
printf("%d\n",f[1]);
}
return 0;
}
后记
被出题人打败了,还好出错的题基本水到了能水的分。