T1:小L的二叉树
【题目描述】
勤奋又善于思考的小L接触了信息学竞赛,开始的学习十分顺利。但是,小L对数据结构的掌握实在十分渣渣。
所以,小L当时卡在了二叉树。 在计算机科学中,二叉树是每个结点最多有两个子结点的有序树。通常子结点被称作“左孩子”和“右孩子”。二叉树被用作二叉搜索树和二叉堆。随后他又和他人讨论起了二叉搜索树。什么是二叉搜索树呢?二叉搜索树首先是一棵二叉树。设key[p]表示结点p上的数值。对于其中的每个结点p,若其存在左孩子lch,则key[p] > key[lch];若其存在右孩子rch,则key[p] < key[rch];注意,本题中的二叉搜索树应满足对于所有结点,其左子树中的key小于当前结点的key,其右子树中的key大于当前结点的key。(因为小L十分喜欢装xx,所以这里他十分装xx的给大家介绍了什么是二叉树和二叉搜索树)。 可是善于思考的小L不甘于只学习这些基础的东西。他思考了这样一个问题:现在给定一棵二叉树,可以任意修改结点的数值。修改一个结点的数值算作一次修改,且这个结点不能再被修改。若要将其变成一棵二叉搜索树,且任意时刻结点的数值必须是整数(可以是负整数或0),所要的最少修改次数。 这一定难不倒聪明的你吧!如果你能帮小L解决这个问题,也许他会把最后的资产分给你1/16哦!
【输入格式】 第一行一个正整数n表示二叉树节点数。节点从1~n进行编号。 第二行n个正整数用空格分隔开,第i个数ai表示结点i的原始数值。 此后n - 1行每行两个非负整数fa, ch,第i + 2行描述结点i + 1的父亲编号fa,以及父子关系ch,(ch = 0 表示i + 1为左儿子,ch = 1表示i + 1为右儿子)。 为了让你稍微减轻些负担,小L规定:结点1一定是二叉树的根哦!
【输出格式】 仅一行包含一个整数,表示最少的修改次数。
【样例输入】 3 2 2 2 1 0 1 1
【样例输出】 2
【数据范围】
20 % :n <= 10 , ai <= 100.
40 % :n <= 100 , ai <= 200
60 % :n <= 2000 .
100 % :n <= 10 ^ 5 , ai < 2 ^ 31.
小L的二叉树
20% :每次DFS后直接暴力找LIS。
40% :可以用 DP 或者贪心或者神奇的暴力等其他奇怪的方法完成。
60% :正解的 LIS 打成 O(n ^ 2)。
100% :首先求出这颗二叉树的中序遍历,那么问题就转换成用最少的修改次数使这个整
数序列严格单调递增。于是很自然的想到了 LIS,但单纯用 LIS 是有一些问题的,
比如这种情况:2 3 1 4, LIS 为 2 3 4,答案求出来为 1,但由于整数的限制,应该
要修改 2 次。即直接 LIS 求出的答案是在非严格递增的情况下的答案。
所以我们将原序列稍加修改,一个常见的将严格递增整数序列映射成非严格递增整
数序列的技巧就是将如下序列:
a1, a2, a3, a4 . an 映射成:
a1 - 1, a2 - 2, a3 - 3, a4 - 4 . an - n.
(这种方法常见于计数类问题)。
这样映射后求最长不下降子序列的长度就没问题了。
标准题解很清楚了,,,这道题处理很巧妙,,因为2,3,2这种序列答案不为1, 把整条串映射为ai-i,再求LIS,n是5000, 所以需要nlogn求LIS,具体见这篇
代码:
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<string>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<algorithm>
#ifdef WIN32
#define AUTO "%I64d"
#else
#define AUTO "%lld"
#endif
#define INF 0x3f3f3f3f
#define clock CLOCKS_PER_SEC
#define cle(x) memset(x,0,sizeof(x))
#define maxcle(x) memset(x,0x7f,sizeof(x))
#define mincle(x) memset(x,-1,sizeof(x))
#define maxx(x1,x2,x3) max(x1,max(x2,x3))
#define minn(x1,x2,x3) min(x1,min(x2,x3))
#define cop(a,x) memcpy(x,a,sizeof(a))
#define FROP "tree"
#define C(a,b) next_permutation(a,b)
#define LL long long
#define smin(x,tmp) x=min(x,tmp)
#define smax(x,tmp) x=max(x,tmp)
using namespace std;
const int N = 1e5+5;
int l[N],r[N],val[N];
int newer[N];
inline int read()
{
int res=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){res=res*10+ch-'0';ch=getchar();}
return res*f;
}
int tot;
void find_mid(int x)
{
if(!x)return;
find_mid(l[x]);
newer[++tot]=x;
find_mid(r[x]);
}
int n;
inline void init()
{
n=read();
for(int i = 1; i <= n; i++)
val[i]=read();
for(int i = 2; i <= n; i++)
{
int fa,ch;
fa=read();
ch=read();
if(ch)r[fa]=i;
else l[fa]=i;
}
}
int last[N],ans;
int find(int x)
{
int l= 1,r=ans,tmp=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(last[mid]<=x)tmp=mid,l=mid+1;
else r=mid-1;
}
return tmp;
}
int main()
{
freopen(FROP".in","r",stdin);
freopen(FROP".out","w",stdout);
init();
find_mid(1);
for(int i = 1;i <= n; i++)
newer[i]=val[newer[i]]-i;
maxcle(last);
for(int i = 1; i <= n; i++)
{
int tmp=find(newer[i]);
if(last[tmp+1]>newer[i])last[tmp+1]=newer[i];
smax(ans,tmp+1);
}
printf("%d",n-ans);
return 0;
}
T2:小L的牛栏
【题目描述】 小L通过泥萌的帮助,成功解决了二叉树的修改问题,并因此写了一篇论文, 成功报送了叉院(羡慕不?)。勤奋又勤思的他在研究生时期成功转系,考入了北京大学光华管理学院!毕业后,凭着自己积累下的浓厚经济学与计算机学的基础,成功建设了一个现代化奶牛场! 奶牛们十分聪明,于是在牛场建围栏时打算和小L斗智斗勇!小L有N种可以建造围栏的木料,长度分别是l1,l2„lN,每种长度的木料无限。
修建时,他将把所有选中的木料拼接在一起,因此围栏的长度就是他使用的木料长度之和。但是聪明的小L很快发现很多长度都是不能由这些木料长度相加得到的,于是决定在必要的时候把这些木料砍掉一部分以后再使用。
不过由于小L比较节约,他给自己规定:任何一根木料最多只能削短M米。当然,每根木料削去的木料长度不需要都一样。不过由于测量工具太原始,小L只能准确的削去整数米的木料,因此,如果他有两种长度分别是7和11的木料,每根最多只能砍掉1米,那么实际上就有4种可以使用的木料长度,分别是6, 7,10, 11。
因为小L相信自己的奶牛举世无双,于是让他们自己设计围栏。奶牛们不愿意自己和同伴在游戏时受到围栏的限制,于是想刁难一下小L,希望小L的木料无论经过怎样的加工,长度之和都不可能得到他们设计的围栏总长度。不过小L知道,如果围栏的长度太小,小L很快就能发现它是不能修建好的。因此她希望得到你的帮助,找出无法修建的最大围栏长度。 这一定难不倒聪明的你吧!如果你能帮小L解决这个问题,也许他会把最后的资产分给你1/8哦!
【输入格式】
输入的第一行包含两个整数N, M,分别表示木料的种类和每根木料削去的最大值。以下各行每行一个整数li(1< li< 3000),表示第i根木料的原始长度。
【输出格式】
输出仅一行,包含一个整数,表示不能修建的最大围栏长度。如果任何长度的围栏都可以修建或者这个最大值不存在,输出-1。
【样例输入】 2 1 7 11
【数据范围】
40 % :1< N<10, 0< M< 300
100 % :1< N< 100, 0< M< 3000
先给出标准题解:
小L的牛栏
40%背包DP
100%两种做法:
第一种:
同余最短路
1.首先预处理出所有能搞出来的原始木棍长度,然后找到一个最小的,记为P。 如果P=1那就不用做下去了,直接输出-1.
2.我们把所有的整数按mod P的值分为P类(mod P=0,1,2,3,4…P-1),记为集合Q0,Q1,Q2…QP-1
如果集合Qi 中有一个长度len可以被组合出来,那么该集合中所有比len大的数也一定可以组合出来.因为是mod P的,所以len可以不断加P来组合出 比它大的且和它在同一个集合里的数。 根据这个性质 就可以 找到图论模型了。
3.我们抽象出P-1个点,分别表示集合Qi 中最小的能被组合出来的数D[i]。 那么把根据原始木棍的长度,可以在这些点之间连边,表示可以从Qi 中的一个数 加 X 得到 Qj中的一个数。
然后利用dijkstra算法 就可以 求出 “集合Qi 中最小的能被组合出来的数”了。 具体实现的时候有个小优化可以减少边的数量,就是如果多条边的权值 mod P 相等,那么只要加入其中的一条就可以了(根据同余定理)。
4.那么如何根据最后D[i]的值来得到答案呢? 还是利用性质“如果集合Qi 中有一个长度len可以被组合出来,那么该集合中所有比len大的数也一定可以组合出来”来做。依次检查每一个D[i],如果D[i]>i,那么集合Qi 中最大的不能被组合出来的数 就是 D[i]-P。检查所有的D[i] 取最大值就是答案了。
5.复杂度,因为建图的复杂度就是O(N^2)的,所以dijkstra的复杂度写成O(N^2)即可。所以总复杂度就是O(N^2)的。
第二种:
关键在于如何判断.
1.若长度可以为1.则任何长度的围栏都可以.
2.若连续v个长度都可以.则上一个不可以的长度为最大值(v是最短的木头长度)
3.若所有木头的长度的最大公约数不为1.说明最大值不存在.
下面写几点自己的理解:
1.这道题的集合最小值是P,但是大于P的其实也可以,只不过集合更多。
2.有两种情况会输出-1, 一种是木条长度可以为1,还有一种是这些木条的gcd!=1, ,,有人问如果dis[i],,也就是mod为i找不到拼接方法不判断么,但是当你找不到是,,说明你的这个拼不到的值是无穷大的,,但在这之前已经判断过了。
3.当找到一个mod=i,,len时,那么在这个集合中,也就是所有modP==i的数中大于len的都可以满足,(无限加P即可),所以我们需要找一个下限,也就是用最短路去更新这个值。
4.dis[u],然后枚举所有木块,当他能够更新另一个mod节点是,这里就是更新最短的地方。
5.其实可以有一个优化,你想,一个dis[u],也许会有很多边加后他的mod=v,所以你只需要加一个最小的边就好了,这个优化代码个人认为不是很懂。
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<string>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<algorithm>
#ifdef WIN32
#define AUTO "%I64d"
#else
#define AUTO "%lld"
#endif
#define INF 0x3f3f3f3f
#define CLOCK CLOCKS_PER_SEC
#define cle(x) memset(x,0,sizeof(x))
#define maxcle(x) memset(x,0x3f,sizeof(x))
#define mincle(x) memset(x,-1,sizeof(x))
#define maxx(x1,x2,x3) max(x1,max(x2,x3))
#define minn(x1,x2,x3) min(x1,min(x2,x3))
#define cop(a,x) memcpy(x,a,sizeof(a))
#define FROP "bullpen"
#define C(a,b) next_permutation(a,b)
#define LL long long
#define smin(x,tmp) x=min(x,tmp)
#define smax(x,tmp) x=max(x,tmp)
using namespace std;
const int N=3005;
inline int read()
{
int res=0,f=1;
char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){res=res*10+ch-'0';ch=getchar();}
return res*f;
}
int gcd(int a,int b){return b==0?a:gcd(b,a%b);}
int n,m,down=INF,up;
int color[N],num[N],cnt;
void init()
{
n=read(),m=read();
for(int i = 1; i <= n; i++)
{
int x=read();
if(x-m<=1)//当可以拼出1时,就-1
{
printf("-1\n");
exit(0);
}
smin(down,x-m);
smax(up,x);
for(int i = 0; i<= m; i++)
color[x-i]=1;//color可以算出的len
}
for(int i = 2; i <= up; i++)
if(color[i])num[++cnt]=i;//cnt个len
bool flag=false;
for(int i= 1; i<= cnt; i++)
{
for(int j= i+1; j<=cnt;j++)
if(gcd(num[i],num[j])==1)//有两个互质,所有的gcd(len)=1
{
flag=true;
break;
}
if(flag)break;
}
if(!flag)
{
printf("-1\n");
exit(0);
}
}
int check(int x)
{
if(x%down==0)return 0;//只需要0->down-1
int tmp=x%down;
while(tmp<x)
{
if(color[tmp])return 1;//如果的出来的mod已经有边可以满足
tmp+=down;
}
return 1;
}
bool used[N];
int dis[N];
void work()
{
maxcle(dis);
dis[0]=0;
for(int i = 1; i < down; i++)
{
int u=down;
for(int j = 0; j< down; j++)
if(!used[j]&&dis[j]<dis[u])u=j;
used[u]=true;
for(int i = 1; i <= cnt; i++)
if(check(num[i]))
{
int v=(dis[u]+num[i])%down;
if(dis[v]>dis[u]+num[i])
dis[v]=dis[u]+num[i];
}
}
}
int main()
{
freopen(FROP".in","r",stdin);
freopen(FROP".out","w",stdout);
init();
work();
int ans=0;
for(int i= 1; i < down; i++)
smax(ans,dis[i]-down);
printf("%d",ans);
return 0;
}
T3:小L的珍珠挂饰
【题目描述】 小L通过泥萌的帮助,成功解决了牛栏的修建问题。奶牛们觉得主人非常厉害,于是再也不敢偷懒,母牛们奋力挤奶,生娃。子子孙孙无穷匮也!小L于是成为了一代富豪! 但是一直困扰小L的就是单身问题!小L经过长久的寻觅,小L终于找到了一个心仪的漂亮妹子。于是,小L打算在520那天给妹子一个惊喜!(虽然小L很节约,但是对妹子还是很阔绰的!) 小L决定用K种珍珠为妹子做一串举世无双的珍珠垂饰。珍珠垂饰是由珍珠连接而成的,其长度可以认为就是珍珠垂饰上珍珠的个数。小L现在腰缠万贯,每种珍珠他都拥有N颗。根据将珍珠垂饰打开后珍珠不同的排列顺序可以区别不同种类的项链。现在,小L好奇自己可以组成多少种长度为1至N的不同的珍珠垂饰?当然,为显富有,每串珍珠垂饰都要必须由K种珍珠连成。 答案取模1234567891。 这一定难不倒聪明的你吧!如果你能帮小L解决这个问题,也许他会把最后的资产分给你1/4哦! 【输入格式】
输入包含多组数据。第一行是一个整数T,表示测试数据的个数。每组数据占一行,包含两个整数N和K,用一个空格隔开。
【输出格式】
每组数据输出仅一行,包含一个整数,表示项链的种类数。
【样例输入】 2 2 1 3 2
【样例输出】 2 8
【数据范围】
40 % :1<= N<=100000, 0<=K<=30
70 % :1<= N<= 1000000000, 0<=K<=30时限 :1000ms
80%~100% :T <= 10, 1<= N<= 1000000000, 0<=K<=30 时限:50ms
还不是很懂,,,先给出标准题解吧。
小L的珍珠挂饰
30%递推
f[i][j]表示长度为i且由j种组成的方案数;
那么 f[i][j]=f[i-1][j-1]*(k-j+1)(表示长度为i时选择j种颜色,必须在(k-j+1)里选择)+f[i-1][j]*j(同理了)
70%矩阵乘法优化上式+快速幂 复杂度(logN * 矩阵乘法复杂度)
100%:容斥原理
先枚举一个n表示长度,那么方案数便是:
kn−C(k,1)∗(k−1)n+C(k,2)∗(k−2)n−C(k,3)∗(k−3)n…..
即nk+∑(−1)i∗C(k,i)∗(k−i)n
然而n太大了,我们不能这样枚举n来做,
故我们试着列出每个n的长度的式子:
kn−C(k,1)∗(k−1)n+C(k,2)∗(k−2)n−C(k,3)∗(k−3)n…..
kn−1−C(k,1)∗(k−1)n−1+C(k,2)∗(k−2)n−1−C(k,3)∗(k−3)n−1…..
kn−2−C(k,1)∗(k−1)n−2+C(k,2)∗(k−2)n−2−C(k,3)∗(k−3)n−2…..
然后我们把每个式子的每一项拿出来,便会发现这是等比数列!
于是我们便可以用等比数列求和公式很好的解决问题了。
等比数列求和
那么我们便来讲一讲等比数列求和的方法:
ai,表示等比数列中第i项,q表示公比
设s=a1+a2+a3…an,
那么qs=a1∗q+a2∗q…an∗q=a2+a3+a4….+an+1
接着我们再把它们相减:
(q−1)s=an+1−a1
故s=an+1−a1q−1
到此便很好得解决问题了。
复杂度O(K^2*等比数列求和)等比数列求和O(logN)所以O(K^2*logN)