2016 Multi-University Training Contest 1

题目:https://cn.vjudge.net/contest/296683

A - Abandoned country

HDU - 5723
求最小生成树,再求这个最小生成树上任意两个点的最短距离期望
任意两个点情况数就是C(n,2),任意两个点的距离和可以转化为求每条边的贡献和
而每条边的贡献就是u->v valsz[v](n-sz[v]用dp[u]储存所有和u相连的边的贡献和,简单树形Dp

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=100010;
const int maxm=1000010;

struct edge{
	int u,v,w,nxt;
}e[maxm];
int head[maxn],tot;
int n,m;
int f[maxn];


void init()
{
	memset(head,-1,sizeof(head));
	tot=0;
}

void addedge(int u,int v,int w)
{
	e[tot].u=u;
	e[tot].v=v;
	e[tot].w=w;e[tot].nxt=head[u];
	head[u]=tot++;
}
int find1(int x)
{
	return x==f[x]?x:f[x]=find1(f[x]);
}
bool union1(int a,int b)
{
	int fa=find1(a),fb=find1(b);
	if(fa!=fb){
		f[fa]=fb;
		return false;
	}
	return true;
}

struct node{
	int a,b,c;
}hh[maxm];
bool cmp(const node& a,const node& b)
{
	return a.c<b.c;
}
ll kruscal()
{
	sort(hh,hh+m,cmp);
	ll sum=0;
	for(int i=0;i<m;i++){
		int a=hh[i].a;
		int b=hh[i].b;
		if(!union1(a,b)){
			sum+=1LL*hh[i].c;
			int c=hh[i].c;
			addedge(a,b,c);
			addedge(b,a,c);
		}
	}
	return sum;
}

int sz[maxn];
double dp[maxn];
void dfs(int u,int fa)//
{
	sz[u]=1;
	for(int i=head[u];~i;i=e[i].nxt){//
		int v=e[i].v,w=e[i].w;
		if(v==fa) continue;
		dfs(v,u);
		sz[u]+=sz[v];
		dp[u]+=dp[v]+(double)(n-sz[v])*sz[v]*w;
	}
}
int main()
{
	int t;
	scanf("%d",&t);
	while(t--){
		init();
		scanf("%d%d",&n,&m);
		int a,b,c;
		for(int i=0;i<m;i++){
			scanf("%d%d%d",&a,&b,&c);
			hh[i].a=a;hh[i].b=b;
			hh[i].c=c;
		}
		for(int i=1;i<=n;i++){
			f[i]=i;
			dp[i]=0;
		}
		ll ans=kruscal();
		printf("%lld ",ans);

		dfs(1,-1);
		printf("%.2f\n",dp[1]*2/n/(n-1));
	}
}

B - Chess

HDU - 5724
题解转载自:https://blog.csdn.net/fsss_7/article/details/51957497
题意:有个n*20的表格,每行有若干棋子,两个人轮流移动。每次移动棋子只能将这枚棋子移动到它右边的第一个空格,同一个位置最多只能有一个棋子,不能移动棋子的人输。
SG函数:https://blog.csdn.net/Danliwoo/article/details/51968789
分析:因为每一行只有20个位置,我们直接用二进制保存,然后计算每个状态的sg值,然后将n行的sg值异或起来就行了,xor=0先手输否则先手胜
状态储存,这里逆着储存状态,高位到地位,方便用前面的结果

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<bitset>
#include<math.h>
#include<vector>
#include<string>
#include<stdio.h>
#include<cstring>
#include<iostream>
#include<algorithm>
#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
const int N=4010;
const int mod=100000000;
const int MOD1=1000000007;
const int MOD2=1000000009;
const double EPS=0.00000001;
typedef long long ll;
const ll MOD=1000000007;
const int MAX=1000000010;
const ll INF=1ll<<55;
const double pi=acos(-1.0);
typedef double db;
typedef unsigned long long ull;
int d[25],f[1050000];
int get(int x) {
    int i,bo=0,w;
    for (i=1;i<=20;i++)
    if ((1<<i)-1==x) return 0;
    memset(d,0,sizeof(d));
    for (i=0;i<20;i++)
    if ((1<<i)>x) break ;
    else if (((1<<i)&x)==0) { bo=1;w=i; }
        else if (bo) {
            d[f[x-(1<<i)+(1<<w)]]=1;
        }
    for (i=0;i<25;i++)
    if (!d[i]) return i;
}
int main()
{
    int i,j,g,x,n,t,sum,ans;
    f[0]=0;for (i=1;i<=(1<<20);i++) f[i]=get(i);
    scanf("%d", &t);
    while (t--) {
        scanf("%d", &n);ans=0;
        for (i=1;i<=n;i++) {
            scanf("%d", &g);sum=0;
            for (j=1;j<=g;j++) {
                scanf("%d", &x);sum+=1<<(20-x);
            }
            ans^=f[sum];
        }
        if (ans) printf("YES\n");
        else printf("NO\n");
    }
    return 0;
}

C - Game

HDU - 5725

题解转载自:https://blog.csdn.net/y1196645376/article/details/52187900/
题意:对于一个nm地图的任意两点的曼哈顿距离和为 n ∗ m ∗ ( n ∗ m − 1 ) ∗ ( n + m ) / 3 n*m*(n*m-1)*(n+m)/3 nm(nm1)(n+m)/3
n ∗ m n*m nm的图上有一些守卫,守卫的位置,满足彼此不同行,不同列,且相邻8格内不会有其他守卫,现等概率的选取起点和终点,所有可以不碰守卫可以到达的路径中,最短路径的期望。
分析:期望等于路径总和除以路径条数。 我们先算路径条数: 对于起点和终点都是等概率随机选择,那么起点和终点组合一共有 ( n ∗ m ) ∗ ( n ∗ m ) (n*m)*(n*m) (nm)(nm)种。然后我们应该去掉起点或者终点为G的组合。怎么去呢?
分类:当起点为G终点不为G的组合有numG * num# ( numG表示G的个数,num#表示#个数). 然后是起点不为G终点为G的组合有numG * num#.起点也是G终点也是G的有numG * num G.三者加起来就是应该去掉的路径数量。有人可能问?如果起点和终点不为G就一定存在路径?答案是是的。因为根据守卫的摆放原则,一定是有解的。(这注意了,可能是题目没有描述清楚,但是通过数据可知为了躲避守卫的路径中可以超越n
m这个地图边界)。
那么剩下来算路径总和: 对于一个 n ∗ m n*m nm地图的任意两点的曼哈顿距离和为 n ∗ m ∗ ( n ∗ m − 1 ) ∗ ( n + m ) / 3 n*m*(n*m-1)*(n+m)/3 nm(nm1)(n+m)/3。 然后我们需要减去起点终点含有G的路径。怎么减呢?只需要对于每个G减去两倍的地图所有点到该点的距离(O(1)可得)。然后我们明显知道。对于起点和终点都是G的情况我们减多了2倍。所以我们两重for循环对于起点终点都是G的组合路径加回来。 然后我们知道,这些路径中有的路径可能会被守卫挡住。需要绕开,并且绕开之后的路径长度并不为该两点的曼哈顿距离。所以我们要计算因为G而导致多走的路径。 因为每条路径最多只会背一个守卫阻挡(根据守卫的摆放规则)。 那么每条路径如果要绕开就会多2的长度。所以我们要计算所有多走的长度。只需要计算有多少条路径需要多走。然后乘2即可。 我们可以发现: 假如地图为: ##G##那么左边两边点和右边两个点之间的路径需要绕开G。这种情况说明如果起点和终点在一条直线上,并且之间有G隔开说明需要绕开。
#G###
###G#
我们发现左上角走到右下角。也需要绕开第一个G或者第二个。这种情况索命。如果起点和终点不在一条直线上。那么如果在横或者竖方向上有一排G拦住。注意这里的一排G并不处于一排。而是保证每列或者每行都有。并且,越靠近起点的另一个方向上的坐标也就越靠近起点。(最后一句可能不好理解。我画图)# #G### ###G#我们可以看到第一排的G必须在第二排G的左边,因为起点在左边,行数越靠近起点的,横坐标越靠近起点。为什么呢? 如果是这样:
###G#
#G###
你认为还能阻拦左上到右下么? 所以满足以上两种情况的路径都应该被计算到绕路路径中,然后让路径总和再加上绕路路径的条数*2。
最后相除就是期望。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<map>
#include<string>
#include<queue>
#include<vector>
#include<list>
using namespace std;
 
typedef long long ll;
typedef pair<ll,ll> ll2;
ll n,m;
char mpt[1005][1005];
ll2 book[1005];
int x[1005],y[1005];
int num;
//n*m的图,计算以(1,1)为起点到所有曼哈顿距离和
ll get(ll n,ll m)
{
    return m*(m-1)*n/2+n*(n-1)*m/2;
}
//n*m的图,计算任意点到所有点的曼哈顿距离和
ll get2(ll x,ll y)
{
    return get(x,y)+get(n-x+1,y)+get(x,m-y+1)+get(n-x+1,m-y+1)-(get(1,x)+get(1,y)+get(1,n-x+1)+get(1,m-y+1));
}
ll get3(int i,int j)
{
    ll a = book[i].first - book[j].first;
    if(a<0) a= -a;
    ll b = book[i].second - book[j].second;
    if(b<0) b= -b;
    return a+b;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        getchar();
        for(int i = 0 ; i < n ; i ++)
            gets(mpt[i]);
        ll ans = n*m*(n*m-1)*(n+m)/3;//n*m的所有点对曼哈顿距离和 
        ll cnt = n*m*n*m;//路径数 
        num = 0;
        memset(x,-1,sizeof(x));
        memset(y,-1,sizeof(y));
        for(int i = 0 ; i < n ; i ++)
            for(int j = 0 ; j < m ; j ++)
                if(mpt[i][j] == 'G')
            {
                book[num++] = ll2(i,j);
                x[i] = j;
                y[j] = i;
                ans -= 2* get2(i+1,j+1);//减去G到所有其他点 
                cnt -= 2 * (n*m-1) + 1;//G到所有其他点的路径数 
            }
        for(int i = 0 ; i < num ; i ++)
            for(int j = 0 ; j < num ; j ++)
                {
                    ans += get3(i,j);//加上G到G 
                    if(i != j)
                        cnt += 1;//加上G到G的路径数 
                }
        for(int i = 0 ; i < num ; i ++)//下面加了2次,这里需要减1次 
        {
            ans -= 4 * book[i].first * ( n - book[i].first - 1);//减去G的左右 
            ans -= 4 * book[i].second * ( m - book[i].second - 1);//减去G的上下 
        }
        //left to right
        ll suml = 0,sumr = 0;
        for(int i = 0 ; i < n ; i ++)
        {
            if(x[i] != -1)
            {
                if(i != 0 && x[i] > x[i-1]) suml += x[i];
                else suml = x[i];
                ans += 4 * suml * (m - x[i] - 1);
                if(i != 0 && x[i] < x[i-1]) sumr += (m - x[i] - 1);
                else sumr = (m - x[i] - 1);
                ans += 4 * sumr * x[i];
            }
            else suml = 0,sumr = 0;
        }
        //down to up
        ll sumu = 0,sumd = 0;
        for(int j = 0 ; j < m ; j ++)
        {
            if(y[j] != -1)
            {
                if(j != 0 && y[j] > y[j-1]) sumu += y[j];
                else sumu = y[j];
                ans += 4 * sumu * (n - y[j] - 1);
                if(j != 0 && y[j] < y[j-1]) sumd += (n - y[j] - 1);
                else sumd = (n - y[j] - 1);
                ans += 4 * sumd * y[j];
            }
            else sumu = 0,sumd = 0;
        }
        double an = (long double)ans/cnt;
        printf("%.4llf\n",an);
    }
    return 0;
}

D – GCD(RMQ+st表)

HDU - 5726

题解转载自:https://blog.csdn.net/viphong/article/details/52013352
首先用st表预处理RMQ的gcd,然后枚举起点【1-n】i
由于gcd一段固定以后具有单调性的
对于每个起点,枚举右端点pos,记下i到pos的gcd为X,则在【pos,n】里二分查找最远的gcd仍为X的下标next,则说明从i开始到pos,与i到next之间的gcd都是同样的,因此mp[x]+=(next-pos+1) 每次case,nlogn预处理st表,预处理所有gcd个数大约时间为nlognlogn,查询o(1)+o(logn)

#include<cstdio>
#include<iostream>
#include<map> 
#include<cmath>
using namespace std;
#define ll long long
const int maxn=1e5+5;

int h[maxn],mx[maxn][17];
map<int,ll> mp;
int n;
int gcd(int a,int b)
{
	return b?gcd(b,a%b):a;
}
void rmq_init()
{
	for(int i=1;i<=n;i++) mx[i][0]=h[i];
	int m=floor(log((double)n)/log(2.0));
	for(int i=1;i<=m;i++){
		for(int j=n;j>0;j--){
		
		//for(int j=1;j<=n;j++){
			mx[j][i]=mx[j][i-1];
			if(j+(1<<(i-1))<=n)
				mx[j][i]=gcd(mx[j][i-1],mx[j+(1<<(i-1))][i-1]);
		} 
	}
}
int rmq_gcd(int l,int r)
{
	int m=floor(log((double)(r-l+1))/log(2.0));
	return gcd(mx[l][m],mx[r-(1<<m)+1][m]);
}
int bin(int st,int l,int r,int pre_gcd)
{
	int ans;
	while(l<=r){//l<r死循环了 
		int m=(l+r)/2;
		if(rmq_gcd(st,m)<pre_gcd)
			r=m-1;
		else
			l=m+1,ans=m;
	}
	return ans;
} 

int main()
{
	int t,cas=1;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n); 
		mp.clear();
		for(int i=1;i<=n;i++)
			scanf("%d",&h[i]);
		rmq_init();
		for(int i=1;i<=n;i++){
			int pre_gcd,pos=i;
			while(pos<=n){
				pre_gcd=rmq_gcd(i,pos);
				int nxt=bin(i,pos,n,pre_gcd);
				mp[pre_gcd]+=(ll)(nxt-pos+1);
				pos=nxt+1; 
			}
		}
		int q,x,y;
		scanf("%d",&q);
		printf("Case #%d:\n",cas++);
		while(q--){
			scanf("%d%d",&x,&y);
			int ans=rmq_gcd(x,y);
			printf("%d %lld\n",ans,mp[ans]); 
		}
	} 
	return 0;
}

E - Necklace

题解转载自:https://blog.csdn.net/xtttgo/article/details/51995159
HDU - 5727
题意:n个阳点和n个阴点,要拼成一个项链,而且阳和阴必须相邻,每个阳的两边都是阴, 每个阴的两边都是阳。然后给你m条边,每条边包括一个编号为a的阳点,和编号为b的阴点,表示这两个如果相邻的话,这个阳点就会变得忧郁。问你最少几个阳点会变忧郁。 思路:最少几个会变的忧郁,那么就是n-最大几个正常的阳点。因为n<=9,所以我们可以考虑枚举阴点的全排列,而且因为是环状,所以我们固定一个点,只要O(8!)就可以枚举阴点的全排列。然后对于阴的每一种排列,我们都可以求出哪些位置可以放哪些阳点。处理出来以后,我们只要对每种排列求一个空位和阳点的最大匹配,就可以求得答案了。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cctype>
#include <string>
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <ctime>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
#define pb push_back
#define mp make_pair
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define calm (l+r)>>1
const int INF = 2139062143;

int n,m;
bool G[11][11];//yin yang
int yin[11];
bool vis[31];
struct EE{
    int to,next;
    EE(){}
    EE(int t,int n):to(t),next(n){}
}edge[500];
int Ecnt,head[31];
inline void add(int a,int b){
    edge[Ecnt]=EE(b,head[a]);
    head[a]=Ecnt++;
}
void build(){
    memset(head,-1,sizeof head);
    Ecnt=0;
    for(int i=1;i<=n;i++){//pos
        int pre=yin[i-1];
        int nxt=yin[i];
        if(i==n)nxt=yin[0];
        memset(vis,false,sizeof vis);
        for(int j=1;j<=n;j++){//yang
            if(G[pre][j]||G[nxt][j]){
                vis[j]=true;//j不能使用
            }
        }
        for(int j=1;j<=n;j++){
            if(!vis[j]){
                add(i,j+n);//i=pos j+n=yang
            }
        }
    }
}
int link[31];
bool dfs(int s){
    for(int i=head[s];~i;i=edge[i].next){
        int t=edge[i].to;
        if(!vis[t]){
            vis[t]=true;
            if(link[t]==-1||dfs(link[t])){
                link[t]=s;return true;
            }
        }
    }
    return false;
}
int match(){
    memset(link,-1,sizeof link);
    int ans=0;
    for(int i=1;i<=n;i++){
        memset(vis,false,sizeof vis);
        if(dfs(i))ans++;
    }
    return ans;
}
int main(){
    while(scanf("%d%d",&n,&m)!=EOF){
        memset(G,0,sizeof G);
        while(m--){
            int a,b;scanf("%d%d",&a,&b);
            G[b][a]=1;//yin->yang
        }
        if(n==0||m==0){
            printf("0\n");continue;
        }
        for(int i=0;i<n;i++){
            yin[i]=i+1;
        }
        int ans=0;
        do{
            build();
            ans=max(ans,match());
        }while(ans!=n&&next_permutation(yin+1,yin+n));
        printf("%d\n",n-ans);
    }
    return 0;
}

F - PowMod(欧拉函数+指数循环节)

题解转载自:https://blog.csdn.net/wust_zzwh/article/details/51966450

HDU - 5728
题意:
在这里插入图片描述
其中n为非平方数
题解:首先是算k:先了解两条性质,欧拉函数是积性函数;
(1)积性函数性质: F ( m 1 ∗ m 2 ) = F ( m 1 ) ∗ F ( m 2 ) F(m1*m2)=F(m1)*F(m2) F(m1m2)=F(m1)F(m2),当且近当 g c d ( m 1 , m 2 ) = 1 gcd(m1,m2)=1 gcd(m1,m2)=1时成立;
(2) φ ( k ∗ p ∗ n ) = p ∗ φ ( k ∗ n ) \varphi(k*p*n)=p*\varphi(k*n) φ(kpn)=pφ(kn),其中p是n的素因子。这个用欧拉函数的素因子表达式很好证明。
有了这个再来算k,题目的n是很特殊的,它的每个素因子的幂次都是1:
那么假设素数p是n的一个素因子,显然gcd(p,n/p)=1;关键是i,如果i中没有素因子p,那么就直接用积性性质。如果i%p==0,必然可以写成i=k*p;即倍数关系,否则i%p!=0;
所以分成两部分求:
在这里插入图片描述
到这里前两和式是可以合并的,考虑和式的上下限,含义不同,第二项的 i i i表示的 p p p的倍数 i ∗ p i*p ip才是第一项i的含义,相当于第二项刚好把第一项补齐了,那么从1到m没有遗漏,而且第二项的i用第一项替换后里面也是 n / p n/p n/p;最终
在这里插入图片描述
这是个二元递归式:n/p和m/p看成整体,那么设原先求的为f(n,m),所以
f(n,m)=(p的欧拉值)*f(n/p,m)+f(n,m/p);
每次枚举一个就够了,n每次要除以p,最多就是把它的每个素因子除完。
第二部分k的超级幂:用欧拉的定理:指数循环节
在这里插入图片描述在这里插入图片描述
每次往幂次上一层模就取一次欧拉值,只有1的欧拉值等一自己,其他数的欧拉值都是小于自己的,所以模会不断变小至1,显然对1取模结果就是0,所以无限就变得有限了

#include<bits/stdc++.h>
using namespace std;
//const int M =10000010,N=1000000007 ;
const int M = 1e7 + 5 ;
const int N = 1e9 + 7 ;
int prime[M],euler[M],s[M];
int res;	
int n,m,p;
void init()
{
	res=0;
	euler[1]=1;
	for(int i=2;i<M;i++)
	{
		if(!euler[i])
		{
		 prime[res++]=i;
		 euler[i]=i-1;	
		}
		for(int j=0;j<res&&i*prime[j]<M;j++)
		{
			if(i%prime[j]==0)
            {
                euler[prime[j]*i]=euler[i]*prime[j];
                break;
            }
            euler[prime[j]*i]=euler[i]*(prime[j]-1);
		}
		
	}
	for(int i=1;i<M;i++)
	s[i]=(euler[i]+s[i-1])%N;
}
 
long long qu(long long a,long long b,long long p)
{
	long long  ans=1;
	a=a%p;
	while(b)
	{
		if(b&1) ans=(ans*a)%p;
		b>>=1;
		a=(a*a)%p;
	}
	return ans%p;
}
long long f(long long k,long long p)
{   
	if(p==1) return 0;// p==2 
	return qu(k,f(k,euler[p])+euler[p],p);
}
long long ans(long long n,long long m)
{
    if( m < 1 ) return 0 ;
    if( m == 1 ) return euler[n];
    if( n == 1 ) return s[m] ;
    if( euler[n] == n-1 )  return ( ans(1,m) * (n-1) % N+ ans(n,m/n) ) % N ;
     for( int i = 2 ; i*i <= n ; i++ )
    {
        if( n%i ) continue ;
        return ((i-1)* ans(n/i,m) % N+ ans(n,m/i) ) % N ;
    } 
}
int main()
{
	init();
	while(~scanf("%d%d%d",&n,&m,&p))
	 cout<< f(ans(n,m),p)<<endl;	
	return 0;
}

G - Rigid Frameworks

HDU - 5729
题解转载自:https://blog.csdn.net/liangzhaoyang1/article/details/52116890
题意:因为矩形是不稳定的,会变成平行四边形,但是可以在矩形对角线加边,通过构成三角形使这个矩形稳定下来。给一 n ∗ m n*m nm的矩形,可以在单位矩形里加两种对角线(从左上到右下,从左下到右上两种),或者不加对角线,或者两条。问使这个 n ∗ m n*m nm的矩形稳定下来的方案数。
先了解下矩形单位格的性质:当一个单位格加上斜边的时候,这个单位格的形状就不能改变,实质上就是组成这个单位格的横边和竖边保持着一个垂直关系.一旦组成这个小单元格的横边和竖边保持了一个垂直关系,那么横边所在的列和竖边所在的行均能保持垂直关系.

题解:原问题等价于求左边有n个点,右边有m个点的联通的二分图的数目 可以用类似连通图计数的方法dp得到,复杂度O(n^6)。
那么如何计算一个二分图的连通方案数?
因为这由 n ∗ m n*m nm个单位矩阵组成,每个单位矩阵可以加两种对角线(从左上到右下,从左下到右上两种),或者不加对角线,共3种选择,则一个n*m矩阵的总方案数是3^ ( n ∗ m ) (n*m) (nm)。只要从所有方案中,除去不合法的方案数,就是合法的方案数。这么做的原因是因为不合法的方案数更容易求(只要二分图是不连通
的,这个方案就是不合法的)。
d p [ n ] [ m ] dp[n][m] dp[n][m]为使 n ∗ m n*m nm矩阵固定下来的合法方案数。

从左边n个点中的点1固定,当连通块大小为i,j时,如何计算非法方案数,从 n-1个点中选出i-1个点(因为点1已经被固定了),从右边m个点中选出j个点,用这i,j个点组成连通块的数量就是,i+j大小的连通块有dp[i][j]个合法的存在方案,同时,下面的
n-i 个点和 m-j 个点有个组合方式。
做法:固定一个点,枚举这个点所处连通块的情况,只要这个连通块不包含二分图中所有的点,则当前的二分图一定是不连通的,一定是非法方案。

所以,转移方程为:
d p [ n ] [ m ] = 3 n ∗ m − C ( n − 1 , i − 1 ) ∗ C ( m , j ) ∗ d p [ i ] [ j ] ∗ 3 ( n − i ) ∗ ( m − j ) dp[n][m]=3^n*m-C(n-1,i-1)*C(m,j)*dp[i][j]*3^(n-i)*(m-j) dp[n][m]=3nmC(n1,i1)C(m,j)dp[i][j]3(ni)(mj)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=40;
const int mod=1e9+7;
long long  fact[MAXN*MAXN],C[MAXN][MAXN],dp[MAXN][MAXN];
 
void init()
{
	fact[0]=1;
	for(int i=1;i<=110;++i) fact[i]=fact[i-1]*3%mod;
	C[0][0]=1;
	for(int i=1;i<MAXN;++i)
	{
		C[i][0]=C[i][i]=1;
		for(int j=1;j<i;++j)
			C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
	}
	for(int i=1;i<=10;++i)
	{
		for(int j=0;j<=10;++j)
		{
			dp[i][j]=fact[i*j];
			if(i==1&&j==0) dp[i][j]=1;
			for(int n=1;n<=i;++n)
			{
				for(int m=0;m<=j;++m)
				{
					if(i==n&&j==m) continue;
					dp[i][j]-=C[i-1][n-1]*C[j][m]%mod*dp[n][m]%mod*fact[(i-n)*(j-m)]%mod;
					dp[i][j]=(dp[i][j]%mod+mod)%mod;
				}
			}
		}
	}
	
}
int main()
{
	int m,n;
	init();
	while(~scanf("%d%d",&n,&m)) 
		printf("%I64d\n",dp[n][m]);
	return 0;
}

H(番外1) - 3-idiots(FFT)

HDU 4609
给定义n条边,求任意取3条,能构成三角形的概率
FFT优化,nlogn,先求出两条边的和的个数,要减去取了两条相同边的方案数,另外因为取边没有顺序,(边1+边2)与(边2+边1)是同一种情况,故每个系数应该除2。
然后处理一个前缀和即可求解,详见代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
#define ll long long
const double pi=acos(-1.0);
//FFT板子
struct Complex{
	double r,i;
	Complex(double r1=0,double i1=0){
		r=r1;i=i1;
	}
	Complex operator +(const Complex &b){
		return Complex(r+b.r,i+b.i);
	}
	Complex operator -(const Complex &b){
		return Complex(r-b.r,i-b.i);
	}
	Complex operator *(const Complex &b){
		return Complex(r*b.r-i*b.i,r*b.i+i*b.r);
	}
};
void change(Complex y[],int len)
{
	for(int i=1,j=len/2;i<len-1;i++){
		if(i<j) swap(y[i],y[j]);
		int k=len/2;
		while(j>=k){
			j-=k;
			k/=2;
		}
		if(j<k) j+=k;
	}
}
// on==1 dft on==-1 idft
void fft(Complex y[],int len,int on)
{
	change(y,len);
	for(int h=2;h<=len;h<<=1){
		Complex wn(cos(-on*2*pi/h),sin(-on*2*pi/h));
		for(int j=0;j<len;j+=h){
			Complex w(1,0);
			for(int k=j;k<j+h/2;k++){
				Complex u=y[k];
				Complex t=w*y[k+h/2];
				y[k]=u+t;
				y[k+h/2]=u-t;
				w=w*wn;
			}
		}
	}
	if(on==-1)
		for(int i=0;i<len;i++)
			y[i].r/=len;
}
const int maxn=400040;
Complex x1[maxn];
int a[maxn/4],n;
ll num[maxn],sum[maxn];
int main()
{
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d",&n);
		memset(num,0,sizeof(num));
		for(int i=0;i<n;i++){
			scanf("%d",&a[i]);
			num[a[i]]++;
		}
		sort(a,a+n);
		int len1=a[n-1]+1;
		int len=1;
		while(len<2*len1) len<<=1;
		for(int i=0;i<len1;i++)
			x1[i]=Complex(num[i],0);
		for(int i=len1;i<len;i++)
			x1[i]=Complex(0,0);
		fft(x1,len,1);
		for(int i=0;i<len;i++)
			x1[i]=x1[i]*x1[i];
		fft(x1,len,-1);
		for(int i=0;i<len;i++)
			num[i]=(ll)(x1[i].r+0.5);
		len=2*a[n-1];
		//剪掉两个相同的组合 
		for(int i=0;i<n;i++)
			num[a[i]+a[i]]--;
		//选择无序,除以2
		for(int i=1;i<=len;i++)
			num[i]/=2;
		sum[0]=0;
		for(int i=1;i<=len;i++)
			sum[i]=sum[i-1]+num[i];
		ll cnt=0;
		for(int i=0;i<n;i++){
			cnt+=sum[len]-sum[a[i]];
			//剪掉一个取大的,一个取小的 
			cnt-=(ll)(n-1-i)*i;
			//剪掉一个取本身,一个取其它 
			cnt-=(n-1);
			//剪掉大于它的取两个的组合 
			cnt-=(ll)(n-1-i)*(n-2-i)/2;
		}
		ll tot=(ll)n*(n-1)*(n-2)/6;
		printf("%.7f\n",(double)cnt/tot);
	}
	return 0;
}

H - Shell Necklace(cdq分治+FFT)

HDU - 5730
题解转载自:https://blog.csdn.net/ACVector/article/details/78268635
题目:一段长为i的项链有a[i]种表达爱意的装饰方法,问长度为n的项链有多少种用上述方式组成的方法。 思路:dp[i]=∑dp[j]*a[i-j],(1<=j<=i-1) 要用fft+CDQ分治
CDQ分治:https://www.cnblogs.com/mlystdcall/p/6219421.html
FFT板子题 4609:https://cn.vjudge.net/problem/HDU-4609
FFT理解:https://blog.csdn.net/WADuan2/article/details/79529900

#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<iostream>
using namespace std;
#define ll long long
const double pi=acos(-1.0);
const int maxn=200050;
const int mod=313;
int a[maxn],dp[maxn];
//FFT板子
struct Complex{
	double r,i;
	Complex(double r1=0,double i1=0){
		r=r1;i=i1;
	}
	Complex operator +(const Complex &b){
		return Complex(r+b.r,i+b.i);
	}
	Complex operator -(const Complex &b){
		return Complex(r-b.r,i-b.i);
	}
	Complex operator *(const Complex &b){
		return Complex(r*b.r-i*b.i,r*b.i+i*b.r);
	}
};
void change(Complex y[],int len)
{
	for(int i=1,j=len/2;i<len-1;i++){
		if(i<j) swap(y[i],y[j]);
		int k=len/2;
		while(j>=k){
			j-=k;
			k/=2;
		}
		if(j<k) j+=k;
	}
}
// on==1 fft on==-1 dfft
void fft(Complex y[],int len,int on)
{
	change(y,len);
	for(int h=2;h<=len;h<<=1){
		Complex wn(cos(-on*2*pi/h),sin(-on*2*pi/h));
		for(int j=0;j<len;j+=h){
			Complex w(1,0);
			for(int k=j;k<j+h/2;k++){
				Complex u=y[k];
				Complex t=w*y[k+h/2];
				y[k]=u+t;
				y[k+h/2]=u-t;
				w=w*wn;
			}
		}
	}
	if(on==-1)
		for(int i=0;i<len;i++)
			y[i].r/=len;
}
int n;
Complex x1[maxn],x2[maxn];
void cdq(int L,int R)
{
	if(L==R){
		dp[L]=(dp[L]+a[L])%mod;
		return;
	}
	int mid=(L+R)/2;
	cdq(L,mid);
	int len=1;
	while(len<(R-L+1)) len<<=1;
	for(int i=0;i<len;i++)
		x1[i]=x2[i]=Complex(0,0);
	for(int i=L;i<=mid;i++)
		x1[i-L]=Complex(dp[i],0);
	for(int i=0;i<R-L+1;i++)
		x2[i]=Complex(a[i+1],0);
	fft(x1,len,1);
	fft(x2,len,1);
	for(int i=0;i<len;i++)
		x1[i]=x1[i]*x2[i];
	fft(x1,len,-1);
	for(int i=mid+1;i<=R;i++){
		dp[i]+=(int)(x1[i-L-1].r+0.5);
		dp[i]%=mod;
	}
	cdq(mid+1,R);
}
int main()
{
	while(~scanf("%d",&n)){
		if(!n) break;
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			a[i]%=mod;
			dp[i]=0;
		}
		cdq(1,n);
		printf("%d\n",dp[n]);
	}
	return 0;
}

I - Solid Dominoes Tilings

HDU - 5731

题解转载自:https://blog.csdn.net/snowy_smile/article/details/52020982
问n×m的格子铺上1×2的骨牌,并且不存在行或列分割线在不跨越骨牌的情况下分隔棋盘

【trick&&吐槽】
做题多就是好。
这道题为——
[稳定多米诺骨牌]:http://www.51nod.com/onlineJudge/problemSolution.html#!problemId=1518
同样也是——
BZOJ 1435 [ZJOI2009]多米诺骨牌:

【题意】
给你一个nm的棋盘,
我们用1
2或2*1(即横着放或竖着放)的骨牌去填充这个棋盘。
问你有多少种填法,使得这个棋盘——
任意相邻两行或两列之间都必须要有一个骨牌横跨。
也就是使得这个棋盘为稳定的。

【类型】
轮廓线DP

【分析】
如果没有"稳定的"这一限制,这题就变成了最基础的轮廓线DP。
这道题的解题步骤是这样子的——
1,基础轮廓线DP
2,容斥

关键是如何容斥。
一开始,我所设想的容斥方法是——
先通过二进制枚举哪些列是断开的,然后通过"奇减偶加"的原则,求出对于nm的矩形,在列不分割条件下的方案数。
然后再逐行枚举,做行容斥——
使得矩形分成上下两块,上面一块内部无行分割,下面一块行分割任意,且上下两块都无列分割
容斥减去(上方案数
下方案数)

不过,这种容斥是错误的。
我这样容斥掉的,是上下都无列分割的方案数。
但是显然,我上下单独的一块,也许是可以有列分割的。
只是在其合并之后没有了列分割。
那要怎么办?

我可以——
先枚举行,再枚举列,然后枚举列分割的状态。
在我们已知列分割状态的基础上,
求出对于长为n,宽为m的图形的合法分割数——
显然,该合法分割数为——

对于分割的列数——奇加偶减
{
内部的容斥是——
枚举上下两部分
上部分内部无行分割
下部分内部随意行分割
计数相乘
}

这里考虑了完整的行列分割状态的影响,所以可以正确AC。
至于复杂度,是有一点儿炸就是了233。

【时间复杂度&&优化】
O(3e7)

#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); }
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b>a)a = b; }
template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b<a)a = b; }
const int N = 0, M = 0, Z = 1e9 + 7, ms63 = 0x3f3f3f3f;
int casenum, casei;

void add(int &x, int y)
{
	if ((x += y) >= Z)x -= Z;
}

//基础DP
int dp[2][1 << 16];
int basic[17][17];	//basic[i][j]表示在不考虑"稳定"的情况下,填充i*j棋盘的方案数
void basicdp()
{
	for (int m = 1; m <= 16; ++m)//枚举宽
	{
		int top = (1 << m) - 1;
		int now = 0, nxt = 1;
		MS(dp[now], 0); dp[now][top] = 1;
		for (int i = 0; i < m; ++i)//逐行DP
		{
			for (int j = 0; j < m; ++j)//逐列DP
			{
				MS(dp[nxt], 0);
				for (int sta = 0; sta <= top; ++sta)
				{
					//横放
					if (j && !(sta & 1 << j - 1) && (sta & 1 << j))
					{
						add(dp[nxt][sta | 1 << j - 1], dp[now][sta]);
					}
					//竖放 && 不放
					add(dp[nxt][sta ^ 1 << j], dp[now][sta]);
				}
				swap(now, nxt);
			}
			basic[i + 1][m] = basic[m][i + 1] = dp[now][top];
		}
	}
}

//容斥DP
int v[17];			//在给定列分隔条件下的无行分割的方案计数
int w[17];			//在给定列分隔条件下的有行分割的方案计数
int p[17];			//列分隔长度
int ans[17][17];
void IEdp()
{
	MS(ans, 0);
	for (int m = 1; m <= 16; ++m)//枚举宽
	{
		int top = (1 << m - 1) - 1;
		for (int sta = 0; sta <= top; ++sta)//枚举列分割状态
		{
			int num = 0;
			for (int i = 0; i < m - 1; ++i)if (sta >> i & 1)p[++num] = i + 1;
			p[++num] = m;
			for (int i = num; i >= 2; --i)p[i] -= p[i - 1];
			for (int i = 1; i <= 16; ++i)//逐行DP
			{
				LL val = 1;
				for (int j = 1; j <= num; ++j)val = val * basic[i][p[j]] % Z;
				w[i] = v[i] = val;
				for (int y = 1; y < i; ++y)add(v[i], Z - (LL)v[y] * w[i - y] % Z);
				if (num & 1)add(ans[i][m], v[i]);
				else add(ans[i][m], Z - v[i]);
			}
		}
	}
	//复杂度2 ^ 16(枚举列分割状态) * C(16,2)(枚举当前行和之前行) * 2(不同个宽度)
	//大概是3e7的复杂度,可以无压力AC。
}

int main()
{
	basicdp();
	IEdp();
	int n, m;
	while (~scanf("%d%d", &n, &m))
	{
		printf("%d\n", ans[n][m]);
	}
	return 0;
}

J - Subway(树的重心+哈希)

题解转载自:https://www.cnblogs.com/cxhscst2/p/8483049.html

HDU - 5732
题意 给定两棵相同的树,但是编号方案不同。求第一棵树上的每个点
对应的第二棵树上的点。输出一种方案即可。
首先确定树的直径的中点。两棵树相等意味着两棵树的直径相等。
然而直径有很多条,我们任意求出两棵树的各一条直径并不以为着
这两条直径是相对应的。但是直径的中点一定是相对应的。
确定根结点后对整棵树哈希并进行匹配的这个过程是不难的。
当直径长度为偶数时中点有两个,那么最多做4次匹配就可以了。
这个哈希函数要好好设计,很容易产生冲突。

#include <bits/stdc++.h>
using namespace std;
#define rep(i, a, b)    for (int i(a); i <= (b); ++i)
#define dec(i, a, b)    for (int i(a); i >= (b); --i)
typedef unsigned long long LL;
 
const LL  A = 90918567;
const LL  B = 87378051;
const LL  mod  = 1e9 + 7;
const int N = 1e5 + 10;
 
int a[N], ans[N], father[N], roota[3], rootb[3];
int numa, numb, n, x, y, c1, c2, L, R, cnt;
bool flag;
map <string, int> mp1, mp2;
vector <int> v[N], g[N];
string ss[N], tt[N], s1, s2;
LL  c[N], f[N];

void init(){
    mp1.clear();
    mp2.clear();
    rep(i, 0, n + 1) v[i].clear(), g[i].clear();
    c1 = 0, c2 = 0;
}

int get1(string s){
    if (mp1.count(s)) return mp1[s];
    mp1[s] = ++c1;
    ss[c1] = s;
    return c1;
}
 
int get2(string s){
    if (mp2.count(s)) return mp2[s];
    mp2[s] = ++c2;
    tt[c2] = s;
    return c2;
}
 
void dfs1(int x, int fa, int now, vector <int> v[N]){
    if (now > c1){
        c1 = now;
        L = x;
    }
 
    for (auto u : v[x]){
        if (u == fa) continue;
        dfs1(u, x, now + 1, v);
    }
}
 
void dfs2(int x, int fa, int now, vector <int> v[N]){
    father[x] = fa;
    if (now > c2){
        c2 = now;
        R = x;
    }
 
    for (auto u : v[x]){
        if (u == fa) continue;
        dfs2(u, x, now + 1, v);
    }
}
 
void gethash_a(int x, int fa){
    vector <LL> val;
    for (auto u : v[x]){
        if (u == fa) continue;
        gethash_a(u, x);
        val.push_back(c[u]);
    }
 
    sort(val.begin(), val.end());
    c[x] = B;
    for (auto u : val){
        c[x] = (c[x] * A ^ u) % mod;
    }
}
 
void gethash_b(int x, int fa){
    vector <LL> val;
    for (auto u : g[x]){
        if (u == fa) continue;
        gethash_b(u, x);
        val.push_back(f[u]);
    }
 
    sort(val.begin(), val.end());
     
    f[x] = B;
    for (auto u : val){
        f[x] = (f[x] * A ^ u) % mod;
    }
}
 
bool cmp_a(const int &a, const int &b){
    return c[a] < c[b];
}
 
bool cmp_b(const int &a, const int &b){
    return f[a] < f[b];
}
 
void work(int x, int y, int fa_a, int fa_b){
    vector <int> na, nb;
    for (auto u : v[x]) if (u != fa_a) na.push_back(u);
    for (auto u : g[y]) if (u != fa_b) nb.push_back(u);
 
    if ((int)na.size() != (int)nb.size()){
        return;
    }
 
    sort(na.begin(), na.end(), cmp_a);
    sort(nb.begin(), nb.end(), cmp_b);
 
    int sz = (int)na.size();
 
    rep(i, 0, sz - 1) if (c[na[i]] == f[nb[i]]) ans[na[i]] = nb[i];
 
 
    rep(i, 0, sz - 1){
        int u1 = na[i], u2 = nb[i];
        work(u1, u2, x, y);
    }
}
 
bool solve(int roota, int rootb){
    gethash_a(roota, 0);
    gethash_b(rootb, 0);
 
    memset(ans, -1, sizeof ans);
    if (c[roota] != f[rootb]) return false;
 
    ans[roota] = rootb;
    work(roota, rootb, 0, 0);
 
    bool ret = true;
    rep(i, 1, n) if (ans[i] == -1){
        ret = false;
        break;
    }
 
    return ret;
}
 
void print(){
    rep(i, 1, n) cout << ss[i] << " " << tt[ans[i]] << endl;
}
 
int main(){
 
    ios::sync_with_stdio(false);
 
    while (cin >> n){
 
        init();
        rep(i, 2, n){
            cin >> s1 >> s2;
            x = get1(s1);
            y = get1(s2);
            v[x].push_back(y);
            v[y].push_back(x);
        }
 
        rep(i, 2, n){
            cin >> s1 >> s2;
            x = get2(s1);
            y = get2(s2);
            g[x].push_back(y);
            g[y].push_back(x);
        }
 
        L = 0, R = 0;
        c1 = 0, c2 = 0;
        dfs1(1, 0, 0, v);
        memset(father, 0, sizeof father);
        dfs2(L, 0, 0, v);
 
        x = R;
        cnt = 0;
        numa = 0;
        while (true){
            a[++cnt] = R;
            R = father[R];
            if (R == 0) break;
        }
 
        if (cnt & 1) roota[++numa] = a[(cnt + 1) / 2];
 
        else{
            roota[++numa] = a[cnt / 2];
            roota[++numa] = a[cnt / 2 + 1];
        }
 
        L = 0, R = 0;
        c1 = 0, c2 = 0;
        dfs1(1, 0, 0, g);
        memset(father, 0, sizeof father);
        dfs2(L, 0, 0, g);
 
        x = R;
        cnt = 0;
        numb = 0;
        while (true){
            a[++cnt] = R;
            R = father[R];
            if (R == 0) break;
        }
 
        if (cnt & 1) rootb[++numb] = a[(cnt + 1) / 2];
 
        else{
            rootb[++numb] = a[cnt / 2];
            rootb[++numb] = a[cnt / 2 + 1];
        }
 
        flag = false;
        rep(i, 1, numa){
            rep(j, 1, numb){
                if (solve(roota[i], rootb[j])){
                    flag = true;
                    print();
                    break;
                }
            }
            if (flag) break;
        }
    }
 
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值