您的位置:首页 > 大数据 > 人工智能

2017 Multi-University Training Contest - Team 1 (?/12) 待补

2017-07-25 19:53 501 查看
比赛链接

官方题解

1001Add More Zero
      水题,因为他要求位数有多少,所以想到对2^m -1 取10的对数就好了,复杂度O(1) .这里要注意这个-1根本没啥用,无影响.

#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
int main()
{
int n, ca = 1;
while(~scanf("%d", &n))
{
int k = log10(2)*n;
printf("Case #%d: %d\n",ca++, k);
}
return 0;
}

 Balala Power!
类似这个oj的这个题 ,不过这个题的范围小,也可以直接搞全排列对其赋值取最大。

官方:

每个字符对答案的贡献都可以看作一个 26 进制的数字,问题相当于要给这些贡献加一个 0 到 25 的权重使得答案最大。最大的数匹配 25,次大的数匹配 24,依次类推。排序后这样依次贪心即可,唯一注意的是不能出现前导
0。

按照权值来贪心,记录每个字母在每个位置上出现的次数,出现的位置越靠前,所分配的数越大,但是也要注意,如果后一位出现的次数加起来大于26则需要向前进位,因为它的前一位也就是比它多26,那么当这个数大于26的时候就相当于他的权重增加了1。

再就是前导0的处理,当有一个字母不能是前导0而我们给他分配为前导0时,就需要注意去贪心的调整使得值尽可能的大,

这里将已经排好序的字母分配的值依次向后调整.

#include<bits/stdc++.h>
#define MOD 1000000007
using namespace std;
const int maxn=1e5+10;
typedef long long ll;
int n;
int num[50];//记录给每个字母分配的值
int lead[50];//前导0
char s[maxn];
ll f[maxn];
void init()
{
f[0]=1;
for(int i=1;i<maxn;i++)
{
f[i]=f[i-1]*26%MOD;
}
return ;
}
struct node
{
int cnt[maxn];//每个字母在每一位出现的次数..权值
int id;
bool operator < (const node &a) const//重载排序
{
for(int i = maxn-1; i >= 0; i--)
{
if(cnt[i] > a.cnt[i]) return 1;
else if(cnt[i] < a.cnt[i]) return 0;
else ;
}
}
}ch[50];
int main()
{
int ca=1;
init();
while(~scanf("%d",&n))
{

memset(ch,0,sizeof(ch));
memset(num,-1,sizeof(num));
memset(lead,0,sizeof(lead));
for(int i=1;i<=n;i++)
{
scanf(" %s",s);
int len=strlen(s);
if(len!=1)
lead[s[0]-'a']=1;
for(int j=0;j<len;j++)
{
ch[s[j]-'a'].cnt[len-j-1]++;//记录在每一位出现的次数
}
}
for(int i=0;i<26;i++)
{
ch[i].id=i;
for(int j=0;j<maxn;j++)
{
if(ch[i].cnt[j]>=26)//判断是否需要进位
{
ch[i].cnt[j+1]+=ch[i].cnt[j]/26;
ch[i].cnt[j]%=26;
}
}
}
sort(ch,ch+26);//这里需要注意,排序后顺序改变,所以加个id,判断为哪个字母.
for(int i=0;i<26;i++)//分配值
{
num[ch[i].id]=26-i-1;
}
for(int i=0;i<26;i++)
{
if(lead[ch[i].id]&&num[ch[i].id]==0)//找前导0
{
for(int j=25;j>=0;j--)
{
if(!lead[ch[j].id])
{
for(int k=25;k>j;k--)
{
num[ch[k].id]=num[ch[k-1].id];//依次调整
}
num[ch[j].id]=0;
break;
}
}
break;
}
}
ll ans=0;
for(int i=0;i<26;i++)
{
for(int j=0;j<maxn;j++)
{
ans=(ans+f[j]*ch[i].cnt[j]*num[ch[i].id]%MOD)%MOD;
}
}
printf("Case #%d: ",ca++);
printf("%lld\n",ans);
}
return 0;
}


Colorful Tree

题意:

一棵n个结点的树,每个结点都有颜色,定义两点之间的路径长度为路径上出现的不同颜色数目,求树上所有路径
的长度和。一共有
n*(n-1)/2条路径.

思路:

首先,第一想法是我们要考虑每一种颜色对路径的贡献,即:有多少条路径经过了颜色为x的点,最后对于每一种颜色有多少条路径经过它做一个和,就可以得出最后答案.

但是,真正的问题是,对于每一种颜色x,有多少路径经过它我们很难得到,所以想到从反面去想,一共有n*(n-1)/2条路径,最多有n*n*(n-1)/2的和,那么我们去求有多少路径没有经过该颜色,从而得到了有多少路径经过了该颜色.
下面考虑怎么去求有多少路径经过了该颜色,考虑连通块,所有不包含颜色x的连通块中的路径条数都是未经过颜色x的.那么问题就转化成了求不包含颜色x的连通块的大小.我们可以对于每一个点,将其去掉跑一边dfs求连通块的个数,但是这样复杂度明显很高.O(n*n)。我们可以换种思路;

举个例子,如图: 



对于上面这个图,对于当前点1,和他颜色相同的点有1 4 8 7 ,那么怎么找他剩下的连通块的个数呢?首先我们知道,以4 8 7 下面的连通块的路径不可能算在1内,因为经过4 8 7 上来都会有和1一样的颜色了,所以要到他们各自的子树中去分别考虑连通块.对于1我们要找到他所有的子树中,所有颜色和他相同的最高点v,然后减掉这些v中所包含的节点个数,就分别得到了每个子树中不包含颜色1的连通块的点数,从而得到了路径,而这些路径就是不经过1的.
如上图: 当前要判断黑颜色时,先到根节点1,找到他的子树2中最高颜色相同的节点4, 得到节点数为5-2=3,在找到它右边的颜色一样的8,3-1=2 ,得到节点数为2,那么也就是1的以2为根节点的子树中,不包含1的一个联通块我们找到了,节点数为2,一条路径. 同理以3为根节点的颜色相同的最好节点为7,连通块为3 6,节点个数为2,一条路径.

上面我也说过了,对于每一个去跑dfs太麻烦,那么有没有什么好办法呢,这里我采用的是dfs序,将整个树转化成一个序列,然后再进行排序,通过dfs序的每个点控制的左右端点,得到不同的颜色将树划分成了几个连通块.这样一次dfs就可以搞定了.
对于每一种颜色,我们给他内部排个序,让他从最高的根节点开始去子树中求连通块,然后查找该节点所控制的范围内是否存在和根节点颜色相同的点,将其减掉,具体方法和上述例子相同.

不会作图...凑合看吧,尴尬.jpg.



假设给你上面这张图,当要求未经过红色的路径有多少时,我们直接将红色排序,直接先找到了四号点,以四号点为根节点找他的子树中 颜色和他相同的最高点,减掉 这些子树中的节点个数,最后得到了连通块,然后再是6 ,7的,但是我们好像落了一些东西吧,没错,我们好像把1 2 3 5这四个点构成的连通块给落下了,这些也是不经过红色点的,所以这时候我们有个小技巧,我们添加一个虚根 0,让0作为这个树的根节点,我让他和所有的颜色都一样,得到如下图



我让0号节点指指向1号点(这是一棵树无论以哪个点为根都无所谓,这里我默认从1).当我要查找红色时我就让0这个根节点颜色为红色,这样他的子树必定为1,然后再按照上述步骤,我在1中找到4 5 7 减去他们子树的节点个数,就得到了1 2 3 5 这四个点构成的连通块数了.

大体的方法就是这样,我也不知道我理解的是否正确,反正这一题我看了一上午到现在,涨了很多姿势.

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=2e5+10;
int n;
int cnt;
vector<int>c[maxn],vt[maxn];
int s[maxn];
int in[maxn],out[maxn],f[maxn];
vector<int> ::iterator it;
void init()
{
for(int i=0;i<=n;i++)
{
c[i].clear();
vt[i].clear();
}
}
void dfs(int x,int pre)//dfs序
{
in[x]=++cnt;
s[x]=1;
f[x]=pre;
for(int i=0;i<vt[x].size();i++)
{
int v=vt[x][i];
if(v==pre)
continue;
dfs(v,x);
s[x]+=s[v];
}
out[x]=cnt;
return ;
}
bool cmp(int x,int y)//重载,左端点的先做根节点.
{
return in[x]<in[y];
}
int main(){

int ca=1;
while(~scanf("%d",&n))
{
init();
int x,y;
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
c[x].push_back(i);//有哪些点对应x这个颜色.
}
for(int i=1;i<n;i++)
{
scanf("%d %d",&x,&y);
vt[x].push_back(y);
vt[y].push_back(x);
}
cnt=0;
vt[0].push_back(1);//添加虚根.
dfs(0,-1);
//cout<<1<<endl;
ll ans=(ll) n*n*(n-1)/2;
for(int i=1;i<=n;i++)
{
if(c[i].empty())
{
ans-= (ll)n*(n-1)/2;
continue;
}
c[i].push_back(0);//保证每一种颜色都是从根节点开始去重.
sort(c[i].begin(),c[i].end(),cmp);
//对于每种颜色排个序.
for(int j=0;j<c[i].size();j++)
{

x=c[i][j];
for(int kk=0;kk<vt[x].size();kk++)//以根节点的每个子节点为根,去寻找最高的颜色相同的点
{
y=vt[x][kk];
if(y==f[x])//建图时为双向边,只找x子节点不找x父节点.
continue;
int size=s[y];//s中存以y为根节点的子树中节点的个数
int k=in[y];
while(1)//在y的子树中查找能否找到和x颜色相同的点.
{

in[n+1]=k;
it=lower_bound(c[i].begin(),c[i].end(),n+1,cmp);//二分查找
if(it == c[i].end()||in[*it]>out[y])
break;
size-=s[*it];
k=out[*it]+1;
}
ans-=(ll)size*(size-1)/2;//减掉不经过x的路径条数.
}
}
}
printf("Case #%d: %lld\n",ca++,ans);
}
return 0;
}


Function

题意:
给出两个序列,一个是0~n-1的排列a,另一个是0~m-1的排列b,现在求满足

的f的个数。

思路:

 假定给你这么一列数
a 2 0 1 ,那么有f函数

f(0)=b[f(a0)]=b[f(2)]

f(2)=b[f(a2)]=b[f(1)]

f(1)=b[f(a1)]=b[f(0)]

那么我们可以看出,当f(0)确定了,那么相应的f1就确定了,f1确定了,f2也就确定了,f2确定了f0确定了,不是一个循环吗?

f0 -f2 -f1-f0 这个确定的是b当中的值,同样的a0-a2-a1-a0这确定的是a中的值,所以这个题目,对于定义域当中的一个循环节,我们在值域b中必须找到和他长度相同的循环节,或者循环节的长度是他的因子.

然后对于每个循环节,找到和他对应的有多少个,最后相乘就好了.

#include<bits/stdc++.h>
#define Ri(a) scanf("%d", &a)
#define Rl(a) scanf("%lld", &a)
#define Rf(a) scanf("%lf", &a)
#define Rs(a) scanf("%s", a)
#define Pi(a) printf("%d\n", (a))
#define Pf(a) printf("%lf\n", (a))
#define Pl(a) printf("%lld\n", (a))
#define Ps(a) printf("%s\n", (a))
#define W(a) while(a--)
#define CLR(a, b) memset(a, (b), sizeof(a))
#define MOD 1000000007
#define inf 0x3f3f3f3f
#define exp 0.00000001
#define  pii  pair<int, int>
#define  mp   make_pair
#define  pb   push_back
using namespace std;
typedef long long ll;
const int maxn=1e5+10;
ll num1[maxn],num2[maxn];
ll a[maxn],b[maxn];
int main()
{
int ca=1;
int n,m;
while(~scanf("%d%d",&n,&m))
{
memset(num2,0,sizeof(num2));
for(int i=0;i<n;i++)
{
scanf("%lld",&a[i]);
}
for(int i=0;i<m;i++)
{
scanf("%lld",&b[i]);
}

int sum=0;
for(int i=0;i<n;i++)
{
ll cnt=0;
int k=i;
while(a[k] != -1)
{
cnt++;
int t=k;
k = a[k];
a[t] = -1;
}
if(cnt)
num1[sum++]=cnt;
}
for(int i=0;i<m;i++)
{
ll cnt=0;
int k=i;
while(b[k]!=-1)
{
cnt++;
int t=k;
k=b[k];
b[t]=-1;
}
if(cnt)
num2[cnt]++;
}
ll ans=1;
for(int i=0;i<sum;i++)
{
ll tt=0;
for(int j=1;j*j<=num1[i];j++)
{
if(num1[i]%j==0)
{
if(j*j==num1[i])
tt+=num2[j]*j;
else
{
tt += num2[j]*j+num2[num1[i]/j]*num1[i]/j;
}
}
}
ans=(ans*tt)%MOD;
}
printf("Case #%d: ",ca++);
printf("%lld\n",ans);
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: