今天又人品爆发,第一二题对了(很多……),虽然第三道题就GG了(那还算很好?)(原谅博主zz)
好了,言归正传……今天并没又考上一次的JOI day2,而是小L系列……
每日推荐
今天继续破例推荐一部番漆黑的子弹
百度云:http://pan.baidu.com/s/1o8MxVTK
小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.
题解(LIS+中序遍历……)
小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.
(这种方法常见于计数类问题)。
这样映射后求最长不下降子序列的长度就没问题了。
思考
自己也是突发奇想,想到了中序遍历然后暴力(lis)的,因为用多重集合存储(其实只知道upper_bound在set中有……居然恰巧躲过了数据的失误(数据以0为父亲节点……))
标答手打LIS
//#include <bits/stdc++.h>
#include<iostream>
#include<cstdio>
#define N 100010
using namespace std;
int n, f[N], T;
struct node
{
int l, r, fa, va;
}t[N];
inline int read()
{
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
return x * f;
}
void dfs(int p)
{
if(p == 0) return;
dfs(t[p].l);
f[++ T] = t[p].va;
dfs(t[p].r);
}
int LIS()
{
int top = 0;
int a[N];
memset(a, 0, sizeof(a));
for(int i = 1; i <= n; i ++)
{
if(top == 0 || f[i] >= a[top]) a[++ top] = f[i];
else
{
int l = 1, r = top;
while(l <= r)
{
int mid = (l + r) >> 1;
if(a[mid] > f[i])r = mid - 1;
else l = mid + 1;
if(a[mid] == f[i])
{
while(a[mid] == f[i]) mid ++;
l = mid;
break;
}
}
a[l] = f[i];
}
}
return top;
}
int main()
{
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
n = read();
for(int i = 1; i <= n; i ++)
t[i].va = read();
for(int i = 2; i <= n; i ++)
{
int k;
t[i].fa = read();
k = read();
if(!k) t[ t[i].fa ].l = i;
else t[ t[i].fa ].r = i;
}
dfs(1);
for(int i = 1; i <= n; i ++)
f[i] -= i;
int ans = n - LIS();
printf("%d",ans);
return 0;
}
time_total=0.24s
(自己)中序遍历+upper_bound
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#include <climits>
#include <cstring>
#include <string>
#include <set>
#include <map>
#include <queue>
#include <stack>
#include <vector>
#include <list>
#define res(i,m,n) for(i=m;i<=n;i++)
const int N=100005;
using namespace std;
int n,m,k,t,b[N][2],a[N];
multiset<int>p;//set只能过90%,多重集合稳定一点
multiset<int>::iterator it;
void add(int x){
it=p.upper_bound(x);
if(it!=p.end())p.erase(it);
p.insert(x);
}
void dfs(int now){
if(b[now][0])dfs(b[now][0]);
add(a[now]-(++t));
if(b[now][1])dfs(b[now][1]);
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
int i,j;
scanf("%d",&n);
res(i,1,n)scanf("%d",&a[i]);
res(i,2,n)scanf("%d%d",&j,&k),b[j][k]=i;
dfs(1);
printf("%d\n",n-p.size());
return 0;
}
time_total=0.75s(是在不想手打LIS,慢0.5s就慢0.5s吧)
小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
【样例输出】
15
【数据范围】
40 % :1< N<10, 0< M< 300
100 % :1< N< 100, 0< M< 3000
题解
小L的牛栏
40%背包DP(因为数据水,手测39000的maxn就可以AC,小编开的是300000,并不会超时,虽然复杂度算出来为O(3000^3)……)
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.说明最大值不存在.
标答
code_first(dp)
#include<ctime>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define NAME "bullpen"
#define N 3005
#define M 300005
#define INF
#define ll long long
using namespace std;
bool a[M],f[M];
int h[M];
int n,m,ans,num;
inline void readin(int &res){
static char ch;
while((ch=getchar())>'9'||ch<'0');
res=ch-48;
while((ch=getchar())<='9'&&ch>='0')
res=res*10+ch-'0';
}
inline void readll(ll &res){
static char ch;
while((ch=getchar())>'9'||ch<'0');
res=ch-48;
while((ch=getchar())<='9'&&ch>='0')
res=res*10+ch-'0';
}
int main(){
freopen(NAME".in","r",stdin);
freopen(NAME".out","w",stdout);
readin(n);readin(m);
for(int x,i=1;i<=n;i++){
readin(x);
for(int j=0;j<=m;j++){
if(x-j>0&&!a[x-j]){
h[++num]=x-j;
a[x-j]=1;
}
}
}
f[0]=1;
for(int i=1;i<=300000;i++){
for(int j=1;j<=num;j++){
if(i-h[j]>=0){
if(f[i-h[j]]){
f[i]=1;
break;
}
}
}
if(f[i]==0)ans=i;
}
if(ans==0||ans==300000)cout<<-1;
else cout<<ans;
return 0;
}
code_second(同余最短路)
//#include <bits/stdc++.h>
#include<iostream>
#include<cstdio>
#define rep(i,n) for(int i = 1; i <= n; i ++)
#define imax(x,y) (x > y? x: y)
#define imin(x,y) (x < y? x: y)
#define N 3010
#define inf 0x7f7f7f7f
using namespace std;
int firste[N],nexte[N*N],v[N*N],w[N*N];
int dist[N],num[N];
bool color[N],used[N];
int n,m,ans, e = 1,low = 83647,top=0,cnt=0;
bool flag=0;
void build_edge(int x,int y,int z)
{
++ e;
nexte[e] = firste[x];
firste[x] = e;
v[e] = y;
w[e] = z;
}
int gcd(int x, int y)
{
if(x > y) swap(x, y);
while(x)
{
int z = x;
x = y % x;
y = z;
}
return y;
}
bool check(int x)
{
if(x == low)return 0;
int tx = x % low;
while(tx < x)
{
if(color[tx])return 0;
tx += low;
}
return 1;
}
int main()
{
freopen("bullpen.in", "r", stdin);
freopen("bullpen.out", "w", stdout);
int x,vans = 0;
scanf("%d%d", &n, &m);
rep(i, n)
{
scanf("%d",&x);
if(x - m <= 1)
{
printf("-1");
return 0;
}
low = imin(low, x - m);
top = imax(top, x);
for(int j = x - m; j <= x; j ++)
color[j] = 1;
}
for(int i = 2; i <= top; i ++)
if(color[i])
num[++ cnt] = i;
for(int i = 1; i < cnt; i ++)
{
for(int j = i + 1; j <= cnt; j ++)
if(gcd(num[i], num[j]) == 1)
{
flag = 1;
break;
}
if(flag)break;
}
if(!flag)
{
printf("-1");
return 0;
}
rep(i, cnt)
if(check(num[i]))
{
int z = num[i] / low;
for(int j = 0; j < low; j ++)
{
int y = (j + num[i]) % low;
if(y > j)
build_edge(j, y, z);
else
build_edge(j, y, z + 1);
}
}
memset(dist, 60, sizeof(dist));
dist[0] = 0;
for(int i = 1; i < low; i ++)
{
int u = low;
for(int j = 0; j < low; j ++)
if(!used[j] && dist[j] < dist[u]) u = j;
used[u] = 1;
for(int p = firste[u]; p; p = nexte[p])
if(dist[v[p]] > dist[u] + w[p])
dist[v[p]] = dist[u] + w[p];
}
for(int i = 0; i < low; i ++)
ans = imax(ans, (dist[i] - 1) * low + i);
printf("%d",ans);
return 0;
}
code_third(gcd)
#include<iostream>
#include<cstdio>
using namespace std;
bool f[5000010], use[3010];
int n, m, i, j, tot, p, now, tem;
int l[3010];
int gcd(int x, int y)
{
if(x > y) swap(x, y);
while(x)
{
int z = x;
x = y % x;
y = z;
}
return y;
}
inline int read()
{
int x = 0, f = 1; char ch = getchar();
while (ch < '0' || ch > '9') { if (ch == '-') f = -1; ch = getchar(); }
while (ch >= '0' && ch <= '9') { x = x * 10 + ch-'0'; ch = getchar(); }
return x * f;
}
int main()
{
freopen("bullpen.in", "r", stdin);
freopen("bullpen.out", "w", stdout);
n = read();
m = read();
for(int i = 1; i <= n; i ++)
{
l[i] = read();
use[ l[i] ] = true;
}
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= m; j ++)
{
if(l[i] - j <= 0) break;
use[l[i] - j] = true;
}
if(use[1])
{
printf("-1");
return 0;
}
tot = 0;
for(int i = 1; i <= 3000; i ++)
if(use[i])
{
tot ++;
l[tot] = i;
}
tem = l[1];
for(int i = 2; i <= n; i ++)
tem = gcd(tem, l[i]);
if(tem != 1)
{
printf("-1");
return 0;
}
i = 0;
f[0] = true;
while(1)
{
i ++;
for(int j = 1; j <= tot; j ++)
{
if(l[j] > i) break;
if(f[i - l[j]]) {f[i] = true; break;}
}
if(f[i] == false) now = 0;
else now ++;
if(now == l[1])
{
printf("%d", i - now);
return 0;
}
}
printf("-1");
return 0;
}
(重头戏)小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)
(小编花了1个小时左右推出了等比数列,but并有理清思路将其实现……)
code
标答
#include<iostream>
#include<cstdio>
#define mod 1234567891
using namespace std;
long long c[32][32];
int n, m, s;
long long fm, an, a1, q, ans;
long long msort(int x, int y)
{
long long ans;
if(y == 1) return x;
else
{
ans = msort(x, y / 2);
ans = (ans * ans) % mod;
if(y % 2 != 0) ans = (ans * x) % mod;
}
return ans;
}
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9')
{
if(ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
x = x * 10 + ch - '0';
ch = getchar();
}
return x * f;
}
int main()
{
freopen("pearl.in", "r", stdin);
freopen("pearl.out", "w", stdout);
int T;
T = read();
c[0][0] = 1;
for(int i = 1; i <= 31; i ++)
for(int j = 1; j <= i; j ++)
c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
for(int t = 1; t <= T; t ++)
{
n = read();
m = read();
ans = 0;
s = 1;
if(m == 1) ans = n;
else
{
for(int i = m; i >= 1; i --)
{
q = i;
an = msort(q, n);
a1 = q;
if(q == 1)
{
fm = 1;
ans = (ans + s * n * c[m + 1][m - i + 1]) % mod;
s = s * (-1);
}
else
{
fm = msort(q - 1, mod - 2);
ans = (((c[m + 1][m - i + 1] * (((((an * q) % mod) - a1 + mod) * fm) % mod))% mod) * s + ans) % mod;
s = s * (-1);
}
}
}
printf("%lld\n", (ans + mod) % mod);
}
return 0;
}
time_total=0.1s
自己的
思路是求逆元,用快速幂暴力求解
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<set>
#include<queue>
#include<algorithm>
#include<vector>
#include<cstdlib>
#include<cmath>
#include<ctime>
#include<stack>
#define rez(i,x,y) for(int i=x;i>=y;i--)
#define res(i,x,y) for(int i=x;i<=y;i++)
#define INF 2100000000
#define ll long long
#define clr(x) memset(x,0,sizeof(x))
#define N 100005
#define MOD 1234567891
#define NAME "pearl"
using namespace std;
inline int readll(ll &res) {
static char ch;
while ((ch = getchar()) < '0' || ch > '9');
res = ch - 48;
while ((ch = getchar()) >= '0' && ch <= '9')
res = res * 10 + ch - 48;
}
ll t,c,g,d,ans,f;
ll p[33];
inline ll mod(ll i,ll j){
ll v=1;
while(j){
if(j&1)v=(v*i)%MOD;
i=(i*i)%MOD;
j>>=1;
}
return v;
}
inline ll jc(ll i){
if(i==1)return c-d+1;
return (((mod(i,c+1)-mod(i,d))%MOD)*mod(i-1,MOD-2))%MOD;
}
inline ll dg(ll i,ll j){
return ((p[i]*mod((p[j]*p[i-j])%MOD,MOD-2))%MOD);
}
int main(){
freopen(NAME".in","r",stdin);
freopen(NAME".out","w",stdout);
readll(t);
p[0]=1;
for(int i=1;i<=30;i++){
p[i]=(p[i-1]*i)%MOD;
}
while(t--){
readll(c);readll(d);
if(c<d){
cout<<0<<endl;
return 0;
}
g=1;ans=0;
for(ll i=d;i>0;i--){
ans=(ans+jc(i)*g*dg(d,d-i))%MOD;
g=-g;
}
while(ans<0){
ans+=MOD;
}
cout<<ans<<endl;
}
return 0;
}
time_total=0.1s(终于改得和标答一样快了,标答毕竟用公式……)
总结
数论知识上还是有些不足(容斥原理),
第二道题的if()也用错了(没有深刻理解就胡乱使用)
以上。
下一步,因时间不足,只能加强对与各种STL的使用,以及基础数论与数据结构模板的了解与灵活运用。