市选总结

4 篇文章 0 订阅

市选总结

By SemiWaker

一头雾水


总述

感觉题目挺奇怪的,很多信息不明确。
做题时还是不够冷静,缺少冷静分析。


题解

二进制编号

题意

求第n个有k个1的01序列。
n107
k10

做题过程

水题

题解

最小的符合条件的序列显然是k个1。
然后多一个0,位置有k种。
再多一个0,这两个0可选的位置有k+1种。
依次类推。
长度为k+p时,有 Cpk+p1 种序列。
那么我们就可以暴力求长度。
这题卡空间,不能直接杨辉三角,这个组合数每次上下都+1,有公式可以直接算 Ck+1n+1=Cknn+1k+1

暴力跑一下,发现k=2的时候只有4500位左右。
k=1显然有n+1位,特判即可。

然后从最高位开始考虑。
设当前是第i位,还有j个k。
如果当前位放0,那么剩下的方案数: Cjp+ki
如果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,当 AiAj1ij1 时,这两个就可以同时保留。
然后变成对 Aii 做最长不下降子序列。

我一开始犯傻直接把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;
}

后记

被出题人打败了,还好出错的题基本水到了能水的分。

By SemiWaker
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值