「2017 Multi-University Training Contest 1」2017多校训练1

1001 Add More Zero(签到题)

题目链接 HDU6033 Add More Zero

找出最大的k,使得\(2^m-1\ge 10^k\)

直接取log,-1可以忽略不计。

#include <cstdio>
#include <cmath>
int cas,m;
int main(){
	while(~scanf("%d",&m)){
		printf("Case #%d: %d\n",++cas,(int)(m*log(2)/log(10)));
	}
	return 0;
}

1002 Balala Power!(贪心)

题目链接 HDU6034 Balala Power!

26个字母可以映射到0~25。问给定的n个字符串代表的26进制数的和的最大值是多少。注意不能有前导零。

将系数提出来就是\(a(k_{a0}\cdot 26^0+k_{a1}\cdot 26^1+...)+b(k_{b0}\cdot 26^0+k_{b1}+...)+...\)
所以可以统计出 \(k[i ][j]\),然后根据k数组给字母们排序,注意如果某个\(k[i][j]\ge 26\),需要进位到\(k[i][j+1]\)
然后就是从小到大依次赋值0~25。但是这样可能有前导零。
所以如果第一个不能为零,就找最小的可以为0放到最前面,它前面的都往后移一位。

#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 100005
#define M 26

using namespace std;
typedef long long ll;
const ll MOD=(1e9+7);

struct Node{
	int id;
	int big;
	bool zero;
}zm[M];

ll p[N];
ll k[M][N];
char s[N];
int cas,n;

void init(){
	p[0]=1;
	for(int i=1;i<N;++i)p[i]=p[i-1]*M%MOD;
}
bool cmp(const Node& a,const Node& b){
	if(a.big==b.big){
		int i;
		for(i=a.big;i&&k[a.id][i]==k[b.id][i];--i);
		return k[a.id][i]<k[b.id][i];
	}
	return a.big<b.big;
}
ll solve(){
	for(int i=0;i<M;++i){
		for(int j=0;j<=zm[i].big;++j){
			if(k[i][j]>=M){
				k[i][j+1]+=k[i][j]/M;
				k[i][j]%=M;
				if(j==zm[i].big)++zm[i].big;//!!!
			}
		}
	}
	sort(zm,zm+M,cmp);

	ll ans=0;
	int i;
	for(i=0;i<M&&!zm[i].zero;++i);
	rotate(zm,zm+i,zm+i+1);
	for(int i=0;i<M;++i){
		for(int j=0;j<=zm[i].big;++j){
			ans+=k[zm[i].id][j]*p[j]*i;
			if(ans>=MOD)ans%=MOD;
		}
    }
	return ans;
}
int main(){
	init();
	while(~scanf("%d",&n)){
		for(int i=0;i<M;++i){
			zm[i].id=i;
			zm[i].big=0;
			zm[i].zero=true;
			memset(k[i],0,sizeof(k[i]));
		}
		for(int i=0;i<n;++i){
			scanf("%s",s);
			int len=strlen(s);
			if(len>1)zm[s[0]-'a'].zero=false;
			for(int j=0;s[j];++j){
				int d=s[j]-'a';
				++k[d][len-j-1];
				zm[d].big=max(zm[d].big,len-j-1);
			}
		}
		printf("Case #%d: %lld\n",++cas,solve());
	}
	return 0;
}

1003 Colorful Tree(dfs)

题目链接 HDU6035 Colorful Tree

n(1e5)个节点的树,已知每个节点颜色,求每条路径的不同颜色数之和。

直接计算每条路径上不同颜色会超时,那就计算每个节点的贡献。每个节点对所有经过它的路径有可能有贡献,因为一条路径上有多个相同颜色的点,所以计算起来就麻烦了。正难则反,全集是所有颜色对所有路径都有贡献,也就是\(n\cdot(n-1)/2\)。补集是每个节点因为不经过它的路径而减少的贡献。然后用 sum[x] 记录以颜色 x 为根的子树有多少个节点。prev 记录访问当前节点 u 时,访问子节点 v 之前的sum[x],那么访问完 v 后,sumv=sum[x]-prev 就是 v 节点为根的子树里 x颜色为根的节点数量。siz[v] 记录子树 v 的总节点数,那么othr=siz[v]-sumv 就是子树 v 里不在x为根的子树里的节点的数量。所以子树 v 对颜色col[u] 减少的路径就是 所有othr个节点里任意两个之间的路径。还要考虑每个颜色最上面的点的上面的节点任意两个的路径,也是不经过该颜色的。

官方题解说这是虚树的思想。

我参考的题解

#include <bits/stdc++.h>
#define N 200001
#define pb(x) push_back(x)
using namespace std;
typedef long long ll;

int n;
vector<int> e[N];
int siz[N];
int col[N];
ll sum[N];
ll ans;
void dfs(int u,int f){
	siz[u]=1;
	ll tot=0;
	for(auto v:e[u]){
		if(v!=f){
			ll prev = sum[col[u]];
			dfs(v,u);  
			siz[u] += siz[v];
			ll sumv = sum[col[u]] - prev;
			ll othr = siz[v] - sumv;
			tot += sumv;
			ans -= (othr-1)*othr/2;
		}
	}
	sum[col[u]]+=siz[u]-tot;
}
int cas;
int cnt;
bool mark[N];
int main(){
	while(~scanf("%d",&n)){
		for(int i=1;i<=n;++i){
			e[i].clear();
			mark[i]=0;
			sum[i]=0;
			cnt=0;
		}
		for(int i=1;i<=n;++i){
			scanf("%d",&col[i]);
			if(!mark[col[i]]){
				mark[col[i]]=1;
				++cnt;
			}
		}
		for(int i=1;i<n;++i){
			int u,v;
			scanf("%d%d",&u,&v);
			e[u].pb(v);
			e[v].pb(u);
		}
		ans=(ll)n*(n-1)/2*cnt;
		dfs(1,0);
		for(int i=1;i<=n;++i)
			if(sum[i]){
				ll up=n-sum[i];
				ans-=(up-1)*up/2;
			}
		printf("Case #%d: %lld\n",++cas,ans);
	}
	return 0;
}

1006 Function(置换)

题目链接 HDU6038 Function

a 是0~n-1的排列,b 是0~m-1 的排列,\(f\) 是定义域为0~n-1,值域为0~m-1 的函数。求有多少符合\(f(i)=b_{f(a_i)}\)的函数\(f\)

官方题解搬运:

考虑置换 a 的一个循环节,长度为 l,

\[f(i) = b_{f(a_i)} = b_{b_{f(a_{a_i})}} = \underbrace{b_{\cdots b_{f(i)}}}_{l\text{ times }b} \]

那么 f(i) 的值在置换 b 中所在的循环节的长度必须为 l 的因数。

而如果 f(i)的值确定下来了,这个循环节的另外 l - 1 个数的函数值也都确定下来了。

答案就是 \(\prod_{i = 1}^{k} \sum_{j | l_i} {j \cdot cal_j}\) ,其中 k 是置换 a 中循环节的个数,\(l_i\) 表示置换 a 中第 i 个循环节的长度, \(cal_j\) 表示置换 b 中长度为 j 的循环节的个数。

——————分割线——————😗

置换 a 的循环节长度为l,就是说 $ \underbrace{a_{...a_i}}_{l 次} = i$ 。

上面那个公式可以写成

\[j=f(i)=b_{...b_{f(a_{..._{a_i}})}}=b_{...b_{f(i)}}=b_{...b_{j}} \]

置换 b 中所在的循环节的长度必须为 l 的因数,就是说循环 l 次必须可以回到 j。

然后我们需要计算出所有循环节长度,对 a 的每个循环节,选一个长度为 j 的 b 的循环节,可以有 j 种映射方案。

注意这里 b 里面的一个循环节允许被不同的a的循环节同时映射。

#include <cstdio>
#include <cstring>
#define mem(a,b) memset(a,b,sizeof(a))
#define N 100001
typedef long long ll;
const ll MOD =1e9+7;
int n,m,cas;
int a[N],b[N],len[N];
bool vis[N];

int main() {
    int n,m;
    while(~scanf("%d%d",&n,&m)) {
        mem(len,0);
        mem(vis,0);
        for(int i=0; i<n; ++i)
            scanf("%d",a+i);
        for(int j=0; j<m; ++j)
            scanf("%d",b+j);
        for(int i=0; i<m; ++i)
            if(!vis[i]) {
                int t=b[i];
                int l=1;
                while(t!=i) {
                    vis[t]=true;//!!
                    t=b[t];
                    ++l;
                }
                ++len[l];
            }
        mem(vis,0);
        ll ans=1;
        for(int i=0; i<n; ++i)
            if(!vis[i]) {
                int t=a[i];
                int l=1;
                while(t!=i) {
                    vis[t]=true;
                    t=a[t];
                    ++l;
                }
                ll res=0;
                for(int j=1; j*j<=l; ++j)
                    if(l%j==0) {
                        if(j*j!=l) res+=(ll)l/j*len[l/j];//!!!
                        res+=(ll)j*len[j];
                        res%=MOD;
                    }
                ans=ans*res%MOD;
            }
        printf("Case #%d: %lld\n",++cas,ans);
    }
    return 0;
}

1011 KazaQ‘s Socks(找规律)

题目链接 HDU6043 KazaQ‘s Socks

每天选最小编号的干净袜子,穿然后脱,脱了有n-1双的时候就全部洗了,且第二天晚上收回来。问第k天穿哪双袜子。

模拟以后发现是

\[\underbrace{1, 2, \cdots, n}_{n\text{ numbers}},\underbrace{1, 2, \cdots, n - 1}_{n - 1\text{ numbers}},\underbrace{1, 2, \cdots, n - 2, n}_{n - 1\text{ numbers}},\underbrace{1, 2, \cdots, n - 1}_{n - 1\text{ numbers}},\underbrace{1, 2, \cdots, n - 2, n}_{n - 1\text{ numbers}},\cdots \]

#include <cstdio>
long long n,k;
int t;
int main(){
	while(~scanf("%lld%lld",&n,&k)){
		printf("Case #%d: ",++t);
		if(k<=n)printf("%lld\n",k);
		else{
			k-=n;
			if(k%(n-1))
				printf("%lld\n",k%(n-1));
			else if((k/(n-1))%2)
				printf("%lld\n",n-1);
			else
				printf("%lld\n",n);
		}
	}
	return 0;
}

1012 Limited Permutation(递归)

题目链接 HDU6044 Limited Permutation

给出每个\((l_i,r_i)\)表示\(p_i\)作为最小值的区间的最大范围。求有多少满足要求的排列p。

map<pair<int,int>,int> 储存区间对应的下标,令 solve(L,R) 表示区间[L,R] 有多少排列方案,那么只要递归计算左右两个区间就可以了,也就是\(solve(L,R)=solve(L,X-1)\cdot solve(R,X-1)\cdot C_{r-l}^{x-1}\)

这题一个是要注意有无解的情况,还要一个是要使用fread优化读入速度。

嗯。。我在Ubuntu上跑题目数据结果段错误了,不过交上去却AC了,求dalao解释。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cctype>
#include <map>
#define mp make_pair
#define pb push_back
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int N = 1000001;
const LL MOD =1e9+7;

char buf[100000],*p1=buf,*p2=buf;
inline char nc(){
	return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline bool rea(int & x){
    char c=nc();x=0;
    if(c==EOF) return false;
    for(;!isdigit(c);c=nc());
    for(;isdigit(c);x=x*10+c-'0',c=nc());
    return true;
}
inline bool rea(LL & x){
    char c=nc();x=0;
    if(c==EOF) return false;
    for(;!isdigit(c);c=nc());
    for(;isdigit(c);x=x*10+c-'0',c=nc());
    return true;
}

int cas;
int n;
int l[N];
map<PII,int> mm;
LL fac[N],inv[N];
LL qpow(LL a,LL b){
	LL ans=1;
	while(b){
		if(b&1)ans=ans*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return ans;
}
void init(){
	fac[0] = inv[0] =1;
	for(int i=1;i<N;++i){
		fac[i]=fac[i-1]*i%MOD;
		inv[i]=qpow(fac[i],MOD-2);
	}
}
LL C(LL n,LL m){
	return n<m?0:(fac[n]*inv[m]%MOD)*inv[n-m]%MOD;
}
LL solve(int l,int r){
	if(l>r+1)return 0;
	if(l==r+1)return 1;
	auto it=mm.find(mp(l,r));
	if(it==mm.end()){
		return 0;
	}
	int x=it->second;
	if(x<l||x>r)return 0;
	if(l==r)return 1;
	
	LL ans=solve(l,x-1)*solve(x+1,r)%MOD*C(r-l, x-l)%MOD;
	return ans;
}
int main(){
	init();
	while(rea(n)){
		mm.erase(mm.begin(),mm.end());
		for(int i=1;i<=n;++i){
			rea(l[i]);
		}
		for(int i=1;i<=n;++i){
			int r;
			rea(r);
			mm[mp(l[i],r)]=i;
		}
		printf("Case #%d: %lld\n",++cas,solve(1,n));
	}
	return 0;
}

ps.这场先补到这里吧,等着我把其它场可以补的补完再看这里的难题吧orz

posted @ 2017-07-29 01:47  水郁  阅读(409)  评论(0编辑  收藏  举报
……