转载请注明出处:http://blog.csdn.net/lhq_er/article/details/73518832#
题目&AC代码见后面
solution:
T1 强迫症 tile.cpp
题目大意:求 ab 和 cd 的最小公倍数,注意:是这两个分数的整倍数。
70%:暴力枚举倍数k
100%:通分后为 adbd 和 bcbd ,所以就是求lcm(ad,bc),lcm(ad,bc)=adbc/gcd(ad,bc)。
T2 手套 gloves.cpp
题目大意:有一行乱序的手套对0~n-1,求把同一对的手套合在一起所需要的最小移动次数,移动方式为相邻两只手套交换位置
30%:?暴搜?
60%:我们考虑一下贪心策略:从左往右枚举,每遇到一只新的手套就把处在它右边的另一只手套找到并移动到它的右边的一个位置。满分算法是对它的优化,下面给出证明(口糊)
证明:设现在移动的两只手套的位置为i,j,那么把两只手套往中间移动所需要的次数step是一样的,若把这副手套移动到原先位置的两边,则所需步数大于step。而且关键是无论怎么移动,剩余手套的相对位置不变。所以我们把两只手套移动的最边上,这样在步数最小的同时对之后移动的影响是最小的,因为若不移动到最边上,剩余手套中夹杂了已经排完序的手套,导致次数增加,故不是最优解。
100%:再仔细一想,我们有了一种更巧妙的做法:先把所有的数按照出现的前后顺序从小到大重新标号,先出现的好编号小,这样上述的模拟过程就是使更新后的序列升序排列,因为最后是有序的,所以移动次数就是这个新序列的逆序对个数。
T3 星座 cross.cpp
题目大意:一棵树中的一个严格的“十字架”的两条边的最大长度和。
50%:暴力枚举每一个度大于等于4的节点作为根节点,dfs求出每棵子树中的最长边,将最大的四个加起来求和,再取其中的最大值即可。
100%:上述方法的暴力之处在于枚举了所有节点,能不能任选一个节点dfs几次就能得出答案呢?我们在思考一下,发现这是一道求树上最值的问题,那能不能用树形DP来做呢?可以!
考虑树上的任意一个节点u,它的最大和只有两种可能:
1.它的前四大儿子
2.它的前三大儿子+父亲的其他孩子的最大值
我们用f[i]表示以i为根节点的子树中的最大的一条从根开始的路径的长度,dfsDP一遍即可,f[u]=max{f[v]+dis[u][v]}v为u的子节点
再定义状态dp[i][0~3]表示以i为节点的整棵树中最大的四条从根开始的路径的长度。
方程就轻松得到了:dp[u]=max{ max{dp[v]+dis[u][v]} , dp[pre]+dis[pre][u]} 注:此处的max都是取前4大个数存到dp[u][0~3]中
总结:
T1水过,T2暴力打炸了,T3暴力50%
最大的感受是T2中灵活运用了逆序对,逆序对在很多题目中都有应用,以后要注意题目能否转化为逆序对求解。
CODE
T1 强迫症 tile.cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll gcd(ll a,ll b)
{
if (a<b) swap(a,b);
if (b==0) return a;
else return gcd(b,a%b);
}
void write(ll a,ll b)
{
ll c=gcd(a,b);
a/=c; b/=c;
if (b==1) printf("%lld\n",a);
else printf("%lld/%lld\n",a,b);
}
int main()
{
ll X,Y,A,B,K,T;
scanf("%lld",&T);
while (T--)
{
scanf("%lld/%lld %lld/%lld",&A,&B,&X,&Y);
K=A*Y*B*X/gcd(A*Y,B*X);
write(K,B*Y);
}
return 0;
}
T2 手套 gloves.cpp
#include<bits/stdc++.h>
using namespace std;
const int MAXN=400010;
int a[MAXN],b[MAXN],n,tot;
long long ans;
void merge(int l,int r,int mid)
{
int i=l,j=mid+1,k=l-1;
while (i<=mid && j<=r)
if (a[i]<=a[j]) b[++k]=a[i++],ans=ans+j-1-mid;
else b[++k]=a[j++];
while (i<=mid) b[++k]=a[i++],ans=ans+r-mid;
while (j<=r) b[++k]=a[j++];
for (i=l;i<=r;i++) a[i]=b[i];
}
void merge_sort(int l,int r)
{
if (l==r) return;
int mid=(l+r)>>1;
merge_sort(l,mid);
merge_sort(mid+1,r);
merge(l,r,mid);
}
int main()
{
scanf("%d",&n);
memset(b,-1,sizeof(b));
for (int i=1;i<=n+n;i++)
{
scanf("%d",&a[i]);
if (b[a[i]]==-1) b[a[i]]=++tot;
a[i]=b[a[i]];
}
merge_sort(1,n+n);
cout<<ans;
return 0;
}
T3 星座 cross.cpp
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int MAXN=100010;
int Head[MAXN],Next[MAXN*2],To[MAXN*2],Key[MAXN*2];
int rank[MAXN],f[MAXN],dp[MAXN][5],n,tot,ans;
void add(int x,int y,int z)
{
tot++;
Next[tot]=Head[x];
Head[x]=tot;
To[tot]=y;
Key[tot]=z;
}
void dfs(int rt,int pre)
{
for (int i=Head[rt];i;i=Next[i])
if (To[i]!=pre) dfs(To[i],rt);
for (int i=Head[rt];i;i=Next[i])
{
int y=To[i],z=Key[i];
if (y!=pre) f[rt]=max(f[rt],f[y]+z);
}
}
void update(int rt,int val)
{
int i=3;
for (i=3;i>=0 && val>dp[rt][i];i--) dp[rt][i+1]=dp[rt][i];
dp[rt][i+1]=val;
}
void dfs_DP(int rt,int pre,int dis)
{
for (int i=Head[rt];i;i=Next[i])
{
int y=To[i],z=Key[i];
if (y==pre) continue;
update(rt,f[y]+z);
}
if (pre!=0 && dp[pre][0]==f[rt]+dis) update(rt,dp[pre][1]+dis);
else update(rt,dp[pre][0]+dis);
for (int i=Head[rt];i;i=Next[i])
{
int y=To[i],z=Key[i];
if (y==pre) continue;
dfs_DP(y,rt,z);
}
}
int main()
{
bool flag=false;
scanf("%d",&n);
for (int i=1;i<=n-1;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z); add(y,x,z);
if (++rank[x]>=4 || ++rank[y]>=4) flag=true;
}
if (!flag) {
cout<<-1;
return 0;
}
memset(f,0,sizeof(f));
memset(dp,-1,sizeof(dp));
dfs(1,0);
dfs_DP(1,0,0);
for (int i=1;i<=n;i++)
if (dp[i][3]!=-1) ans=max(ans,dp[i][0]+dp[i][1]+dp[i][2]+dp[i][3]);
cout<<ans;
return 0;
}
Problem
NOIp2017模拟题 day1
强迫症
问题描述
人行道铺着两行地砖,第一行每块的长度是A/B,第二行每块的长度是X/Y。两行砖块第一块的一边是对齐的。
作为一个强迫症患者,看到这样的地砖你很不爽,于是就想知道,最少隔多少距离后两行地砖的缝隙又会对齐。
输入格式
输入第一行包含一个整数T,表示测试点组数。
接下来T行,每行两个分数,格式为A/B X/Y,两个分数中间用一个空格隔开。
输出格式
T行,每行包含一个分数(若答案为整数则输出整数),表示每组数据的答案。分数必须以最简形式输出。
样例输入
2
3/2 5/8
4/3 3/10
样例输出
15/2
12
数据范围
30%的数据A,B,X,Y<=20
70%的数据T<=10
100%的数据1<=A,B,X,Y,<=10,000,T<=100,000
手套
问题描述
你现在有N对手套,但是你不小心把它们弄乱了,需要把它们整理一下。N对手套被一字排开,每只手套都有一个颜色,被记为0~N-1,你打算通过交换把每对手套都排在一起。由于手套比较多,你每次只能交换相邻两个手套。请你计算最少要交换几次才能把手套排整齐。
输入格式
输入第一行一个N,表示手套对数。
第二行有2N个整数,描述了手套的颜色。每个数都在0~N-1之间,且每个数字都会出现恰好两次。
输出格式
一行,包含一个数,表示最少交换次数。
样例输入
2
0 1 0 1
样例输出
1
数据范围
30%的数据N≤9;
60%的数据N≤1000;
100%的数据N≤200,000。
星座
问题描述
星空中有n颗星星,有n-1对星星间被人为地连上了线,每条连线有各自的长度。所有星星被连成了一个整体。现在,你要在星系中找到一个最大的十字形星座。即,你要找到两条星星构成的路径,使得它们恰好有一颗公共星(这颗公共星不能是某条路径的端点),且两条路径的长度和最大。
左图红线表示了一个合法的十字形星座,而右图的星座并不合法。
输入格式
第一行一个数n,表示星星的数量。
接下来n行,每行3个数x,y,z,表示第x颗星星和第y颗星星间有一条连线,它的长度是z。
输出格式
一行,包含一个整数,表示最大的路径长度和。若答案不存在,输出-1。
样例输入
10
3 8 6
9 3 5
1 9 2
4 8 6
2 3 3
10 4 8
5 9 5
7 2 3
6 9 1
样例输出
33
数据范围
20%的数据n<=1000
50%的数据n<=10,000
100%的数据n<=100,000,0<=z<=1000
最后再来一份作者的Solution
NOIp2014模拟题 day1 题解
p1
70% 我们的目标是找最小的k,使得(a*k/b)/(x/y)是整数。枚举k后判断即可。
100% 算gcd(a*y,b*x),不多说了。
p2
30% 搜索
60% 可以采取如下贪心策略:从左到右扫描手套的序列,若当前手套的颜色为x,则找到颜色x的另一只手套(很显然在右边),将它向左交换至第一只旁边,继续扫描。
100% 按上述贪心做法,我们可以在O(N)的时间内确定最后的颜色序列。相同颜色的手套之间必然不会发生交换,于是我们就可以确定每只手套的最终位置了。问题转化为:给出一个序列,其中的元素各不相同,求最少交换几次能变成另一个序列。将序列的元素置换一下后,这个问题可以进一步转化为最少交换几次可以将数列排好序,即求一个数列的逆序对个数,可以用归并排序或者数据结构(线段树、树状数组等)解决。
p3
20% 枚举公共点,求出它每个出度方向上到其它点的最长路径长度,取前4长的加起来。
50% 其实暴力是能过50%的数据的,只要在枚举公共点时先判断一下出度是否大于4即可。这是为了告诉大家就算写暴力也要把必要的优化加上。
100% 任选一个树根,DP,f[i,0..3]分别表示从i开始,向下走到某个叶子为止最长、第二长、第三长、第四长的路径长度。从叶子向树根的顺序DP,每次用f[i,0]去尝试更新i的父亲,可以很方便地求出f数组。用g[i]表示以i为起点,第一步向i的父亲方向走的最长路径长度。第一步从i走到i的父亲fa[i]后,第二步有两种选择,第一种是继续往父亲走,则最长长度为g[fa[i]],第二种是向下走。为了使路径最长首选当然是f[fa[i],0],但如果i处在f[fa[i],0]对应的那条路径上,那就只能选择f[fa[i],1]了。有了f数组和g数组后,枚举公共点i,答案为max{f[i,0]+f[i,1]+f[i,2]+f[i,3] , g[i]+f[i,0]+f[i,1]+f[i,2]}。