山西NOIp四校联考部分解题报告
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
⼭西NOIp 四校联考部分解题报告
D1T1 Color
题⽬描述
⼀个1*n 的⽅格图形 (不可旋转),⽤m 种颜⾊填涂每个⽅格。
每个⽅格只能填涂⼀种颜⾊,如果有相邻的⽅格颜⾊相同则视为可⾏⽅案,问有多少种可⾏⽅案。
.
输⼊格式:
两个正整数:m n
输出格式:
⼀个数:可能的填涂⽅式总数,mod100003。
输⼊样例:
2 3
输出样例:
6
样例解释:
6种可能的填涂⽅式:0,0,0; 0,0,1; 0,1,1; 1,0,0; 1,1,0; 1,1,1
对于10%的数据:对于另外50%的数据:对于100%的数据:分析
1. 20分做法:暴⼒搜索。
考场上对于数学题的⼀般⽅法。
2. 其他思路:暴⼒打表找规律法。
3. NB ⽅法:排列组合【我不会有⽊有】。
⾸先利⽤暴⼒搜索打⼀张表,包含了n = 1..8, m = 1..8的答案。
然后开始找规律吧。
⾄于怎么找,那就是仁者见仁智者见智了。
我⾸先设为n 个⽅格m 种颜⾊的可⾏⽅案,
为不可⾏⽅案。
显然有。
然后⽤某种灵感构造⼀个数列。
对照表,发现总有。
发现了半个杨辉三⾓,根据⼆项式定理,猜想,⽤归纳法:
m =2,n <33
1<m <10000,1<n <=106
1<=m <=,1<=n <=1081012
f (n )
g (n )g (n )=−f (n )m n
h (n )=m ×h (n −1)+−h (n −1)m n −1f (n )=h (n )−(m −1)n −1h (0)=0
h (1)=1
h (2)=2m −1
h (3)=3−3m +1
m 2h (4)=4−6+4m −1
m 3m 2h (n )=−(m −1m n )2(0)=−(m −1=000
原式得证。
因此,。
利⽤快速幂便可以AC 。
为什么要描述这种⽅法呢?注意,考信息不是考数学,计算机不是⽩给你的。
要充分利⽤计算机暴⼒快的特点,结合⼀些奇技淫巧解决问题!代码略。
D1T2 Rigel
题⽬背景
南欧包括:国家1:罗马尼亚、国家2:保加利亚、国家3:塞尔维亚、国家4:马其顿、国家5:阿尔巴尼亚、国家6:希腊、国家7:斯洛⽂尼亚、国家8:克罗地亚、国家9:波斯尼
亚……。
⼀个名叫Rigel 的oier 受学校之托乘坐⽕车通往各个国家处理CCF 相关事情。
题⽬描述
Rigel 决定通过⼀条铁路线去往各个国家,铁路依此连接国家1,国家2…国家n 。
该铁路经过n 个国家,每个国家都有⼀个车站。
经过两国之间必须购买车票,第k 段铁路连接了国
家k 和国家k+1。
为推⼴本国运输业,每个国家除了发放单次车票Ak 。
也发放IU 卡,使⽤IU 卡的好处是多次往来于该国家可以节约费⽤,⼀次乘车费是,但是该
卡有⼯本费Ck.(Iu 卡可以⽆限充值).
Rigel 要去m 个国家,从城市R1开始分别按照R1,R2,R3…Rm 的顺序前往各个国家,可能会多次到达⼀个国家。
相邻访问的国家位置不⼀定相邻,且不会是同⼀个国家。
他想知道这⼀趟处理事务下来,⾄少会花掉多少的钱,包括购买单次车票、买IU 卡和充值的总费⽤。
输⼊格式:
第⼀⾏两个整数,n ,m 。
接下来⼀⾏,m 个数字,表⽰Ri
接下来n-1⾏,表⽰第k 段铁路的Ak,Bk,Ck
输出格式:
⼀个整数,表⽰最少花费
输⼊样例:
9 10
3 1
4 1
5 9 2
6 5 3
200 100 50
300 299 100
500 200 500
345 234 123
100 50 100
600 100 1
450 400 80
2 1 10
输出样例:
6394
说明
2-3和8-9为单次车票,其余都是买卡充值。
- 对于30%数据
- 对于另外30%数据
- 对于100%的数据 ,其中分析
就是luogu ⽉赛的海底⾼速嘛。
由于是离线处理,容易想到⽤差分数组。
⼀个差分上去,前缀累加,再判断每⼀段怎么样便宜即可。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
long long n, m;
long long c[100005];
long long a[100005];
int main()
{
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
memset(c, 0, sizeof c);
scanf("%lld%lld", &n, &m);
for (int i = 1; i <= m; i++)
scanf("%lld", &a[i]);
for (int i = 1; i < m; i++) {
long long from = a[i], to = a[i+1];
if (from > to) swap(from, to);
c[from]++;
c[to]--;
}
long long sum = 0;
long long sigma = 0;
for (int i = 1; i < n; i++) {
sum += c[i];
long long ai, bi, ci;
//printf("%d ", sum);
scanf("%lld%lld%lld", &ai, &bi, &ci);
if (ai*sum <= sum*bi+ci) sigma += ai*sum;
else sigma += sum*bi+ci;
}
printf("%lld\n", sigma);
return 0;
}
h (0)=−(m −1=0
m 0)0h (n )=m ×h (n −1)+−h (n −1)
m n −1=[−(m −1](m −1)+m n −1)n −1m n −1
=(m −1+1)−(m −1m n −1)n
=−(m −1m n )n
f (n )=h (n )−(m −1=−(m −1−(m −1)n −1m n )n )n −1Bk (Bk <Ak )m =2n <=1000,m <=1000m ,n <=100000Ak ,Bk ,Ck <=100000
D2T1 Area
题⽬描述
⼩康同学在纸上画了N个平⾏于坐标轴的矩形,这N个矩形可能有互相覆盖的部分,他想知道这些矩形的总⾯积是多少。
注意,互相覆盖的部分只能算⼀次。
.
输⼊格式:
输⼊第⼀⾏为⼀个数N(1≤N≤100),表⽰矩形的数量。
下⾯N⾏,每⾏四个整数,分别表⽰每个矩形的左下⾓和右上⾓的坐标,坐标范围为–10^8到10^8之间的整数。
输出格式:
输出只有⼀⾏,⼀个整数,表⽰图形的⾯积。
输⼊样例:
3
1 1 4 3
2 -1
3 2
4 0
5 2
输出样例:
10
数据范围
对于40%的数据:n<=40,坐标绝对值<1000
对于100%的数据:n<=100,坐标绝对值<10^8
分析
⾸先bs这题超纲。
1. 40分做法:⼆维差分数组前缀和累加。
毫⽆脑⽔。
2. 100分做法:离散化或漂浮法。
漂浮法更好些,于是⽤漂浮。
漂浮法是计算⼏何中计算重叠矩形⾯积的⽅法。
具体来说,就是把矩形放⼊⽔中,分为n层,每层⼀个。
从最底端的开始上浮,碰到上⾯的矩阵就裂开成若⼲块,直到上浮到
顶,就是要求的⾯积。
可以看成你在⽔⾯向下俯视,所能看到的就是要浮上来的,也就是总⾯积。
⼀个trick:判断矩形相交。
假定矩形是⽤⼀对点表达的(minx,miny)(maxx, maxy),那么两个矩形rect1{(minx1,miny1)(maxx1, maxy1)}, rect2{(minx2,miny2)(maxx2, maxy2)} 相交的结果⼀定是个矩形,构成这个相交矩形rect{(minx,miny)(maxx, maxy)}的点对坐标是: minx = max(minx1, minx2)
miny = max(miny1, miny2)
maxx = min(maxx1, maxx2)
maxy = min(maxy1, maxy2)
如果两个矩形不相交,那么计算得到的点对坐标必然满⾜
minx > maxx 或者 miny > maxy
由于中间有⼏个步骤没想清楚,分类⽐较多。
代码冗长。
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
struct obj {
struct point {
long long x, y;
point(){}
point(long long a, long long b) : x(a), y(b) {}
};
point left_bot, right_top;
obj() {}
obj(point a, point b):left_bot(a), right_top(b) {}
obj(long long a, long long b, long long c, long long d)
{
*this = obj(point(a, b), point(c, d));
}
operator long long() const
{
return (right_top.x-left_bot.x)*(right_top.y-left_bot.y);
}
}arr[105];
long long ans = 0;
inline bool fail(long long a, long long b, long long c, long long d)
{
return a >= c || b >= d;
}
void lift_up(obj rect, int i)
{
if (i == 0) {
ans += rect;
return;
}
obj ths = arr[i];
long long minx = max(ths.left_bot.x, rect.left_bot.x);
long long miny = max(ths.left_bot.y, rect.left_bot.y);
long long maxx = min(ths.right_top.x, rect.right_top.x);
long long maxy = min(ths.right_top.y, rect.right_top.y);
//printf("%lld %lld %lld %lld\n--\n", minx, miny, maxx, maxy);
if (fail(minx, miny, maxx, maxy))
lift_up(rect, i-1);
else {
long long a = rect.left_bot.x, b = rect.left_bot.y, c = rect.right_top.x, d = rect.right_top.y;
long long e = minx, f = miny, g = maxx, h = maxy;
if (!fail(a, b, e, d))
lift_up(obj(a, b, e, d), i-1);
if (!fail(e, b, g, f))
lift_up(obj(e, b, g, f), i-1);
if (!fail(e, h, g, d))
lift_up(obj(e, h, g, d), i-1);
if (!fail(g, b, c, d))
lift_up(obj(g, b, c, d), i-1);
}
}
int main()
{
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
ios::sync_with_stdio(false);
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
long long a, b, c, d;
cin >> a >> b >> c >> d;
if (fail(a, b, c, d))
arr[i] = obj(0, 0, 0, 0);
else
arr[i] = obj(a, b, c, d);
}
for (int i = n; i >= 1; i--)
lift_up(arr[i], i-1);
cout << ans << endl;
return 0;
}
D2T3 review
题⽬描述
转眼间⼜到期中考试了,⼩康同学开始了紧张的复习。
教材中总共有N个要复习的知识点,有些知识点之间还存在着单向联系,例如知识点A→B,只要掌握了前置的知识点A,知识点B也能顺利掌握。
⼩康给每个知识点进⾏了评估,并标出它的相应分值。
只要掌握了这个知识点,就获得相应的分数。
⼩康同学现在要选择K个知识点开始复习,使得可以获得最⼤的分值。
输⼊描述
第⼀⾏:N K (分别表⽰知识点的个数和选择的知识点)
以下N⾏,每⾏两个整数a和b
(a表⽰这个知识点单向连接的下⼀个知识点,如果a=0,表⽰这个知识点没有单向连接的下⼀个知识点,b表⽰这个知识点的分值)
输出描述
仅⼀个数,表⽰最⼤得分。
样例输⼊
8 2
0 1
1 1
2 100
2 100
0 1
5 1
6 2
6 2
样例输出
202
数据范围
对于30%的数据:n<=1000,k<=30
对于60%的数据:n<=50000,k<=100
对于100%的数据:n<=200000,k<=500,1<=b<=1000000
分析
⾸先说明这个数据弱了:没有环。
事实上,题⽬描述根本没有排除有环的情况,例如:我学了冒泡,可以轻易掌握选择;学了选择,也可以轻易掌握冒泡。
这就形成了⼀个环。
所以,标程是有漏洞的!
⾸先看到数据规模,应该不难想到缩点建图。
由于每个点的出度都为0或1,因此,在同⼀个连通分量中,不会存在两个或以上的环【同《Noip2015 信息传递》】,且任意⼀个环在缩点后是没有出边的。
利⽤kosaraju缩点建图(⽐常规要简单⼀些),不难证明,得到图的转置图必然是森林!。
那么必然要选叶⼦。
怎么选呢?贪⼼!考虑任意两个选择,如果两个选择位于同⼀个连通分量内,则先后⽆所谓;如果不是,那先选择结果较⼤的不可能差于先选结果较⼩的。
因此只需要每次选结果最⼤的,然后修改其他值【选择这个叶⼦会把其祖先⼀起选择,因此会影响到同⼀连通分量内的其他叶⼦】,重复选择k次即可。
这⾥⽤线段树实现。
代码过于坑,考场上⼀不⼩⼼就爆0了。
改了⼀句就AC……
#include <iostream>
#include <cstdio>
#include <cctype>
#include <stack>
#include <cstring>
#include <queue>
using namespace std;
#define maxn (1<<21)
typedef long long ll;
#define lc (k<<1)
#define rc ((k<<1)+1)
struct zkw_heap {
ll dat[1<<22];
int from[1<<22];
zkw_heap()
{
memset(dat, 0, sizeof dat);
for (int i = maxn; i <= maxn+maxn-1; i++)
from[i] = i-maxn+1;
}
void modify(int i, ll j)
{
int p = i+maxn-1;
dat[p] = j;
for (int k = p>>1; k; k>>=1)
if (dat[lc] >= dat[rc]) {
dat[k] = dat[lc];
from[k] = from[lc];
} else {
dat[k] = dat[rc];
from[k] = from[rc];
}
// cout << "--tree " << from[1] << " " << dat[1] << "--\n";
}
}zkw;
ll read()
{
ll a = 0;
int c;
do c = getchar(); while(!isdigit(c));
while (isdigit(c)) {
a = a*10+c-'0';
c = getchar();
}
return a;
}
struct graph {
struct p {
int to, next;
}edge[200005];
int head[200005], top;
graph()
{
top = 0;
memset(head, 0, sizeof head);
}
void push(int i, int j)
{
edge[++top].to = j;
edge[top].next = head[i];
head[i] = top;
}
} anti, new_anti;
int normal[200005], dealed[200005];
int dfn[200005], top = 0;
int group[200005];
int vis[200005];
int rd[200005], anti_leave[200005];
ll ranks[200005];
ll rk[200005];
int n, gp = 0, k;
void get_dfn(int i)
{
vis[i] = 1;
if (!vis[normal[i]] && normal[i] != 0)
get_dfn(normal[i]);
dfn[++top] = i;
//cout << "dfn[" << top << "] = " << i << endl;
}
void dfs(int i, int gp)
{
vis[i] = 1;
group[i] = gp;
//cout << i << " in group " << gp << endl;
rk[gp] += ranks[i];
for (int k = anti.head[i]; k; k = anti.edge[k].next) {
int to = anti.edge[k].to;
if (!vis[to] && to != 0)
dfs(to, gp);
}
}
int leaves[200005], num = 0;
long long dp[200005];
long long get_rank(int i)
{
if (dp[i] != -1) return dp[i];
if (i == 0) return 0;
return dp[i] = get_rank(dealed[i]) + rk[i];
}
queue<int> que;
void change(int i)
{
// cout << "changing " << i << endl;
vis[i] = 1;
if (dealed[i] && !vis[dealed[i]])
change(dealed[i]);
while (!que.empty()) que.pop();
que.push(i);
while (!que.empty()) {
int k = que.front(); que.pop();
if (rd[k] == 0) {
zkw.modify(anti_leave[k], dp[k] - dp[i]);
//dp[k] -= dp[i];
}
for (int p = new_anti.head[k]; p; p = new_anti.edge[p].next) if (!vis[new_anti.edge[p].to])
que.push(new_anti.edge[p].to);
}
}
int main()
{
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
memset(vis, 0, sizeof vis);
memset(dealed, 0, sizeof dealed);
memset(anti_leave, 0, sizeof anti_leave);
memset(dp, -1, sizeof dp);
memset(rd, 0, sizeof rd);
n = read(); k = read();
for (int i = 1; i <= n; i++) {
normal[i] = read();
ranks[i] = read();
if (normal[i])
anti.push(normal[i], i);
}
for (int i = 1; i <= n; i++)
if (!vis[i])
get_dfn(i);
memset(vis, 0, sizeof vis);
for (int i = n; i >= 1; i--)
if (!vis[dfn[i]])
dfs(dfn[i], ++gp);
for (int i = 1; i <= n; i++)
if (group[normal[i]] != group[i]) {
dealed[group[i]] = group[normal[i]];
new_anti.push(group[normal[i]], group[i]);
rd[group[normal[i]]]++;
}
// for (int i = 1; i <= gp; i++)
// cout << rk[i] << " ";
// puts("");
// 缩点建图
for (int i = 1; i <= gp; i++)
if (rd[i] == 0) {
leaves[++num] = i;
anti_leave[i] = num;
//cout << i << endl;
}
memset(vis, 0, sizeof vis);
for (int i = 1; i <= num; i++) {
zkw.modify(i, get_rank(leaves[i]));
//cout << leaves[i] << " " << get_rank(leaves[i]) << endl; }
/*for (int i = 1; i <= gp; i++)
cout << dp[i] << " ";
puts("");*/
long long ans = 0;
for (int i = 1; i <= k; i++) {
//cout << zkw.from[1] << endl;
int lvs = leaves[zkw.from[1]];
//vis[lvs] = 1;
ans += zkw.dat[1];
// cout << lvs << " " << zkw.dat[1] << endl;
int mark = zkw.from[1];
if (!vis[dealed[lvs]] && dealed[lvs])
change(dealed[lvs]);
zkw.modify(mark, 0);
/* for (int i = 1; i <= gp; i++)
cout << dp[i] << " ";
puts("");*/
}
cout << ans << endl;
return 0;
}。