题目链接:http://hihocoder.com/contest/msbop2015round2a/problems
第一题:彩色的树</p><p>题意:给定一棵n个节点的树,节点编号为1, 2, …, n。这是一棵彩色的树,每个节点恰好可以染一种颜色。初始时,所有节点的颜色都为0。现在需要实现两种操作:1. 改变节点x的颜色为y;2. 询问整棵树被划分成了多少棵颜色相同的子树。即每棵子树内的节点颜色都相同,而相邻子树的颜色不同。</p><p>输入:第一行一个整数T,表示数据组数,以下是T组数据。每组数据第一行是n,表示树的节点个数。接下来n - 1行每行两个数i和j,表示节点i和j间有一条边。接下来是一个数q,表示操作数。之后q行,每行表示以下两种操作之一:1. 若为"1",则询问划分的子树个数。2. 若为"2 x y",则将节点x的颜色改为y。</p><p>思路:一开始的思路是用一个数组表示颜色,每次更新通过遍历变换颜色点的邻接点来更新子树值,但是会TLE。实际上如果用stl的map来多记录一些信息,那么每次更新的时间是较小的(map的红黑树)。map[i][j]保存的内容是i结点的儿子中颜色为j的个数。维护过程见代码。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
#define N 100005
int s[N];
struct edge{
int y,next;
}e[N<<1];
int first[N],top,f[N];
int T,c,n,q,op,res;
map<int,int> mm[N];
void add(int x,int y){
e[top].y = y;
e[top].next = first[x];
first[x] = top++;
}
void dfs(int x,int fa){
int i,y;
f[x] = fa;
for(i = first[x];i!=-1;i=e[i].next){
y = e[i].y;
if(y == fa)
continue;
dfs(y,x);
mm[x][0]++;
}
}
void update(int a,int b){
if(mm[a].find(s[a]) != mm[a].end()) //如果儿子中有颜色和当前结点a相同的,那么是变换后多出来的子树
res += mm[a][s[a]];
if(mm[a].find(b) != mm[a].end()) //如果儿子中有颜色和变换后的颜色b相同的,那么是变换后需要减少的子树
res -= mm[a][b];
if(f[a]){ //如果有父节点
if(s[f[a]] == s[a])
res++;
if(s[f[a]] == b)
res--;
mm[f[a]][b]++; //更新父节点的子节点颜色值
mm[f[a]][s[a]]--;
}
s[a] = b;
}
int main(){
scanf("%d",&T);
for(c = 1;c<=T;c++){
int i,a,b;
scanf("%d",&n);
for(i = 1;i<=n;i++)
mm[i].clear();
memset(s, 0, sizeof(s));
memset(first, -1, sizeof(first));
top = 0;
res = 1;
for(i = 1;i<n;i++){
scanf("%d %d",&a,&b);
add(a,b);
add(b,a);
}
printf("Case #%d:\n",c);
dfs(1,0);
scanf("%d",&q);
while(q--){
scanf("%d",&op);
if(op == 1)
printf("%d\n",res);
else{
scanf("%d %d",&a,&b);
update(a,b);
}
}
}
return 0;
}
第二题:建造金字塔
题意:金字塔是一个底边在x轴上的等腰直角三角形。你是二次元世界的一个建筑承包商。现在有N个建造订单,每个订单有一个收益w,即建造此金字塔可获得w的收益。对每个订单可以选择建造或不建造。建造一个金字塔的成本是金字塔的面积,如果两个或多个金字塔有重叠面积,则建造这些金字塔时重叠部份仅需建造一次。建造一组金字塔的总利润是收益总和扣除成本。现给出这些订单,请求出最大利润。
输入:输入数据第一行为一个整数T,表示数据组数。每组数据第一行为一个整数N,表示订单数目。接下来N行,每行三个整数x, y, w,表示一个订单。(x, y)表示建造出的金字塔的顶点,w表示收益。
思路:动态规划。首先可以知道,我们只需要关心每个三角形的右边界点即可。这样才能包含一个完整的三角形。因此,对于所有的三角形,用状态(l,r,w)来描述它。
接下来,定义d(j)表示区间[0,j]上收益的最大值。事先我们要统计出最大边界lim,这样j的变化范围就是0≤j≤lim。下面考虑如何寻找状态转移方程。
(1)当j≥x[i].r时,表示第i个三角形完全包括在[0,j]之间,取建造它与不建造它收益的较大者。即d(j)=max{d(j),d(j)+x[i].w};
(2)当不满足(1)时但j≥x[i].l时,我们关注的是x[i].r处的收益最大值,画图后容易知道,收益增加值是建造第i个金字塔的收益w减去多建设的面积,即S(x[i].l,x[i].r)-S(x[i].l,j)。即得到如下状态转移方程:d(x[i].r)=max{d(x[i].r),d(j)+x[i].w-S(x[i].l,x[i].r)+S(x[i].l,x[i].r)};
(3)当前两个均不满足时,状态转移方程其实和(2)类似,即d(x[i].r)=max{d(x[i].r),d(j)+x[i].w-S(x[i].l,x[i].r)};
最后,不要忘记一种特殊情况:只建造第i个金字塔时候的收益,因此最后还要取上述计算出的收益和只建造第i个金字塔收益的较大者。
当所有区间的收益最大值计算完毕后,答案就是他们中的最大值。注意事先要把d(j)都初始化为无穷小,表示没有计算过。(参考了http://www.bkjia.com/ASPjc/990122.html)
感悟:动态规划作为算法中最重要的部分,可谓十分灵活。这次比赛从资格赛到两场初赛都有动态规划的题目,但是类型均不同。所以还是要经常总结并且灵活应用动态规划。
#include <cstdio>
#include <cstring>
#include <algorithm>
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define N 1005
using namespace std;
struct node{
int x,y,w;
}p[N];
double dp[N<<1];
int T,n,m,c;
int cmp(node a,node b){
return a.x<b.x;
}
double area(int x,int y){
return (y-x)*(y-x)/4.0;
}
int main(){
scanf("%d",&T);
for(c = 1;c<=T;c++){
int i,j,a,b;
double res = 0;
scanf("%d",&n);
for(i = 1,m=0;i<=n;i++){
scanf("%d %d %d",&a,&b,&p[i].w);
p[i].x = a-b;
p[i].y = a+b;
m = max(m,a+b);
}
sort(p+1,p+1+n,cmp);
for(i = 0;i<=m;i++)
dp[i] = -0x3fffffff;
for(i = 1;i<=n;i++){
for(j = m;j>=0;j--){
if(j>=p[i].y)
dp[j] = max(dp[j], dp[j]+p[i].w);
else if(j>p[i].x)
dp[p[i].y] = max(dp[p[i].y], dp[j]+p[i].w-area(p[i].x, p[i].y)+area(p[i].x, j));
else
dp[p[i].y] = max(dp[p[i].y], dp[j]+p[i].w-area(p[i].x,p[i].y));
}
dp[p[i].y] = max(dp[p[i].y] , p[i].w-area(p[i].x,p[i].y));
}
for(i = 0;i<=m;i++)
res = max(res, dp[i]);
printf("Case #%d: %.2lf\n",c,res);
}
return 0;
}
第三题:质数相关
题意:两个数a和 b (a<b)被称为质数相关,是指a × p = b,这里p是一个质数。一个集合S被称为质数相关,是指S中存在两个质数相关的数,否则称S为质数无关。如{2, 8, 17}质数无关,但{2, 8, 16}, {3, 6}质数相关。现在给定一个集合S,问S的所有质数无关子集中,最大的子集的大小。
输入:第一行为一个数T,为数据组数。之后每组数据包含两行。第一行为N,为集合S的大小。第二行为N个整数,表示集合内的数。
思路:如果将集合内的所有数当做定点,两个数如果质数相关则添边,那么原题转化为求图的最大独立集。如果图是任意图,那么这个NPC问题无法在多项式时间内求出,但是仔细分析可知这个图不可能存在圈,所以必然是一个二部图。接下来进行二部图染色即可,选取每个连通分支中颜色多的那种进行求和即可。
其他的,用求最大匹配的方法亦可(为什么定点只需1~n就可以?不用分出两部来吗)。
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
#define N 1005
#define M 500000
int n,c,T;
int s[N];
int first[N<<1],top,flag[M+5],used[N];
struct edge{
int y,next;
}e[N*N/2];
int col[N][2],len,res;
void prime(){ //筛质数,质数为0,合数为1
int i,j;
flag[1] = 1;
for(i = 2;i<M;i++)
if(!flag[i])
for(j = i<<1;j<=M;j+=i)
flag[j] = 1;
}
void add(int x,int y){
e[top].y = y;
e[top].next = first[x];
first[x] = top++;
}
void dfs(int x,int c){
int i;
used[x] = 1;
col[len][c]++;
for(i = first[x];i!=-1;i=e[i].next)
if(!used[e[i].y])
dfs(e[i].y, 1-c);
}
int main(){
prime();
scanf("%d",&T);
for(c = 1;c<=T;c++){
int i,j;
top = len = res = 0;
memset(used, 0, sizeof(used));
memset(first, -1, sizeof(first));
memset(col, 0, sizeof(col));
scanf("%d",&n);
for(i = 1;i<=n;i++)
scanf("%d",&s[i]);
sort(s+1,s+n+1);
for(i = 1;i<n;i++)
for(j = i+1;j<=n;j++){
if(s[j]%s[i]==0 && !flag[s[j]/s[i]])
add(i,j),add(j,i);
}
for(i = 1;i<=n;i++){
if(!used[i])
dfs(i,0);
len++;
}
for(i = 0;i<len;i++)
res += max(col[i][0],col[i][1]);
printf("Case #%d: %d\n",c,res);
}
return 0;
}
通过最大匹配求最大独立集:
#include <cstring>
#include <algorithm>
using namespace std;
#define N 1005
int n,c,T;
int s[N];
int first[N<<1],top,used[N],link[N];
struct edge{
int y,next;
}e[N*N/2];
int isprime(int x){
int i;
if(x==2)
return 1;
for(i = 2;i*i<=x;i++)
if(x % i == 0)
return 0;
return 1;
}
void add(int x,int y){
e[top].y = y;
e[top].next = first[x];
first[x] = top++;
}
int dfs(int i){
int j,y;
for(j = first[i];j!=-1;j=e[j].next){
y = e[j].y;
if(!used[y]){
used[y] = 1;
if(link[y] == -1||dfs(link[y])){
link[y] = i;
return 1;
}
}
}
return 0;
}
int hungary(){
int i,res = 0;
for(i = 1;i<=n;i++){
memset(used, 0, sizeof(used));
if(dfs(i))
res++;
}
return res;
}
int main(){
scanf("%d",&T);
for(c = 1;c<=T;c++){
int i,j;
memset(first, -1, sizeof(first));
memset(link, -1, sizeof(link));
top = 0;
scanf("%d",&n);
for(i = 1;i<=n;i++)
scanf("%d",&s[i]);
sort(s+1,s+n+1);
for(i = 1;i<n;i++)
for(j = i+1;j<=n;j++)
if(s[j]%s[i]==0 && isprime(s[j]/s[i]))
add(i,j),add(j,i);
printf("Case #%d: %d\n",c,n-hungary()/2);
}
return 0;
}