noip2013 总结
转圈游戏
题目
n 个小伙伴(编号从 0 到 n-1)围坐一圈玩游戏。按照顺时针方向给 n 个位置编号,从0 到 n-1。最初,第 0 号小伙伴在第 0 号位置,第 1 号小伙伴在第 1 号位置,……,依此类推。游戏规则如下:每一轮第 0 号位置上的小伙伴顺时针走到第 m 号位置,第 1 号位置小伙伴走到第 m+1 号位置,……,依此类推,第n − m号位置上的小伙伴走到第 0 号位置,第n-m+1 号位置上的小伙伴走到第 1 号位置,……,第 n-1 号位置上的小伙伴顺时针走到第m-1 号位置。
现在,一共进行了 10^k轮,请问 x 号小伙伴最后走到了第几号位置。
输入输出格式
输入格式:
输入文件名为 circle.in。
输入共 1 行,包含 4 个整数 n、m、k、x,每两个整数之间用一个空格隔开。
输出格式:
输出文件名为 circle.out。
输出共 1 行,包含 1 个整数,表示 10^k 轮后 x 号小伙伴所在的位置编号。
样例
样例输入
10 3 4 5
样例输出
5
说明
数据范围
对于 30%的数据,0 < k < 7;
对于 80%的数据,0 < k < 10^7;
对于 100%的数据,1 <n < 1,000,000,0 < m < n,1 ≤ x ≤ n,0 < k < 10^9
思路
- 先通过草稿手动模拟,可以得到规律,最后的位置为(x+m*10^k)%n
- 很明显,算m*10^k需要用到快速幂
- 数据范围:为了防止爆long long要不断%n
代码
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;
long long n;
long long pow(int root, int time) {
int ans=1;
while(time) {
if(time & 1) ans=(root*ans)%n;
root=(root*root)%n;
time>>=1;
}
return ans%n;
}
int main() {
long long m,k,x;
long long t,r;
cin>>n>>m>>k>>x;
r=0;
t=pow(10,k);
t*=m;
t%=n;
r=(x+t)%n;
cout<<r<<endl;
return 0;
}
火柴排队
题目
涵涵有两盒火柴,每盒装有 n 根火柴,每根火柴都有一个高度。 现在将每盒中的火柴各自排成一列, 同一列火柴的高度互不相同, 两列火柴之间的距离定义为: ∑(ai-bi)^2
其中 ai 表示第一列火柴中第 i 个火柴的高度,bi 表示第二列火柴中第 i 个火柴的高度。
每列火柴中相邻两根火柴的位置都可以交换,请你通过交换使得两列火柴之间的距离最小。请问得到这个最小的距离,最少需要交换多少次?如果这个数字太大,请输出这个最小交换次数对 99,999,997 取模的结果。
输入输出
输入:
输入文件为 match.in。
共三行,第一行包含一个整数 n,表示每盒中火柴的数目。
第二行有 n 个整数,每两个整数之间用一个空格隔开,表示第一列火柴的高度。
第三行有 n 个整数,每两个整数之间用一个空格隔开,表示第二列火柴的高度。
输出:
输出文件为 match.out。
输出共一行,包含一个整数,表示最少交换次数对 99,999,997 取模的结果
思路
- 先来证明一个公式:
若a1>a2且b1>b2,则有(a1-b1)^2 +(a2-b2)^2 < (a2-b1)^2 + (a1-b2)^2
当然这个公式很容易证,拆开就好了- 然后运用这个公式,发现为保证火柴距离最小,两列火柴对应的两根火柴在各列中高度的排名应该相同
- 再来定义一个r数组,使得r[a[i].num]=b[i].num,则可以很惊奇地发现,交换次数即为r数组中逆序对的个数,此题得解
现在考虑求逆序对
用归并排序求:
实际上归并排序的交换次数就是这个数组的逆序对个数,为什么呢?
- 归并排序是将数列a[l,h]分成两半a[l,mid]和a[mid+1,h]分别进行归并排序,然后再将这两半合并起来。
- 在合并的过程中(设l<=i<=mid,mid+1<=j<=h),当a[i]<=a[j]时,并不产生逆序数;当a[i]>a[j]时,在
- 前半部分中比a[i]大的数都比a[j]大,将a[j]放在a[i]前面的话,逆序数要加上mid+1-i。因此,可以在归并排序中的合并过程中计算逆序数.
用树状数组求:
- 每输入一个b[i],就用a[b[i]+1]++去标记总共输入多少次了(因为输入的数据是0~n-1,所以输入的b[i]就相当于a[i]的下标i,而且有0,树状数组无法对下标为0进行操作,所以要a[b[i]+1]);
- 对于一个b[i],要想查询它前面有多少大于它的,只需将a[b[i]+1]到a[n]加起来,也就是求一段数组的和,那么树状数组就上场加速了
代码
归并排序
#include<cstdio>
#include<algorithm>
using namespace std;
typedef struct n{
int num,ord;
}node;
node first_team[100010],second_team[100010];
int a[100010],b[100010],ans;
int compare(node x,node y)
{
return x.num<y.num;
}
void Merge(int l,int r)
{
if(l>=r) return ;
int mid=(l+r)/2;
Merge(l,mid);
Merge(mid+1,r);
int i=l,j=mid+1,k=l;
while(i<=mid&&j<=r)
{
if(a[i]>a[j])
{
b[k++]=a[j++];
ans+=mid-i+1;
ans%=99999997;
}
else b[k++]=a[i++];
}
while(i<=mid) b[k++]=a[i++];
while(j<=r) b[k++]=a[j++];
for(int i=l;i<=r;i++)
a[i]=b[i];
}
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&first_team[i].num);
first_team[i].ord=i;
}
for(int i=1;i<=n;i++)
{
scanf("%d",&second_team[i].num);
second_team[i].ord=i;
}
sort(first_team+1,first_team+n+1,compare);
sort(second_team+1,second_team+n+1,compare);
for(int i=1;i<=n;i++)
a[first_team[i].ord]=second_team[i].ord;
Merge(1,n);
printf("%d",ans);
return 0;
}
树状数组
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 100010;
const int maxm = 99999997;
struct MyStruct
{
int data;
int loc;
}a[maxn],b[maxn];
int e[maxn], n, c[maxn];
int inline readint()
{
int x = 0;
char c = getchar();
while (c<'0' || c>'9') c = getchar();
while (c >= '0'&&c <= '9')
{
x = x * 10 + c - '0';
c = getchar();
}
return x;
}
int lowbit(int x)
{
return x&-x;//树状数组实现
}
void add(int x,int t)
{
while (x <= n)
{
e[x] += t;
e[x] %= maxm;
x += lowbit(x);//每次往后加,可以改变后面对应的和
}
}
int sum(int x)
{
int s = 0;
while(x)
{
s += e[x];
s %= maxm;
x -= lowbit(x);//得到所求的和
}
return s;
}
bool cmp(MyStruct x, MyStruct y)
{
return x.data < y.data;
}
int main()
{
n = readint();
for (int i = 1; i <= n; i++)
{
a[i].data = readint();
a[i].loc = i;//记录位置
}
for (int i = 1; i <= n; i++)
{
b[i].data = readint();
b[i].loc = i;
}
sort(a + 1, a + n + 1, cmp);
sort(b + 1, b + n + 1, cmp);
for (int i = 1; i <= n; i++)
{
c[a[i].loc] = b[i].loc;//离散优化
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
add(c[i], 1);//离散优化后大小就是正确顺序的位置
ans += i - sum(c[i]);//当前位置,减去之前比他大的数的个数
ans %= maxm;
}
printf("%d", ans);
return 0;
}
积木大赛
题目
春春幼儿园举办了一年一度的“积木大赛”。今年比赛的内容是搭建一座宽度为n的大厦,大厦可以看成由n块宽度为1的积木组成,第i块积木的最终高度需要是hi。
在搭建开始之前,没有任何积木(可以看成n块高度为 0 的积木)。接下来每次操作,小朋友们可以选择一段连续区间[l, r],然后将第第 L 块到第 R 块之间(含第 L 块和第 R 块)所有积木的高度分别增加1。
小 M 是个聪明的小朋友,她很快想出了建造大厦的最佳策略,使得建造所需的操作次数最少。但她不是一个勤于动手的孩子,所以想请你帮忙实现这个策略,并求出最少的操作次数。
输入输出格式
输入格式:
输入文件为 block.in
输入包含两行,第一行包含一个整数n,表示大厦的宽度。
第二行包含n个整数,第i个整数为hi 。
输出格式:
输出文件为 block.out
仅一行,即建造所需的最少操作数。
输入输出样例
样例输入
5
2 3 4 1 2
样例输出
5
思路
其实就是简单的贪心,观察样例;
a[1]=2 所以位置1一定被操作了两次 ans+=2
a[2]-a[1]=1 位置2比位置1多操作一次 ans+=1
但a[i]<a[i-1]时,跳过。因为对于a[i]<a[i-1]的情况,a[i]和a[i-1]一定不在一次操作内
以此类推,就得到最后的答案
代码
/*很简短*/
#include<iostream>
#include<cstdio>
#define MAXX 100000+5
using namespace std;
int a[MAXX],n;
long long ans=0;
int main() {
scanf("%d",&n);
for (int i=1; i<=n; i++) {
scanf("%d",&a[i]);
if (a[i]>a[i-1]) ans+=a[i]-a[i-1];
}
printf("%lld",ans);
return 0;
}
货车运输
题目描述
A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 q 辆货车在运输货物, 司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。
输入输出
输入格式:
输入文件名为 truck.in。
输入文件第一行有两个用一个空格隔开的整数 n,m,表示 A 国有 n 座城市和 m 条道路。 接下来 m 行每行 3 个整数 x、 y、 z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z 的道路。注意: x 不等于 y,两座城市之间可能有多条道路 。
接下来一行有一个整数 q,表示有 q 辆货车需要运货。
接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市运输货物到 y 城市,注意: x 不等于 y 。
输出格式:
输出文件名为 truck.out。
输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货车不能到达目的地,输出-1。
输入输出样例
输入样例:
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3
输出样例#1:
3
-1
3
说明
数据范围
对于 30%的数据,0 < n < 1,000,0 < m < 10,000,0 < q< 1,000;
对于 60%的数据,0 < n < 1,000,0 < m < 50,000,0 < q< 1,000;
对于 100%的数据,0 < n < 10,000,0 < m < 50,000,0 < q< 30,000,0 ≤ z ≤ 100,000。
思路
- 求一条路径的最小边的最大值,符合该条件的路径一定在最大生成树上
- 在最大生成树上跑LCA倍增。即求起点到LCA上最小边和终点到LCA上最小边的最小值
代码
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<iostream>
using namespace std;
const int inf = 100001;
const int maxn = 10010;
const int maxm = 50050;
int n, m, q, tot;
int st[maxn], vis[maxn], deep[maxn], fa[maxn][17], g[maxn][17], f[maxn];
struct node{
int v, w, next;
} edge[maxm];
struct node1{
int u, v, w;
} a[maxm];
bool cmp(node1 x, node1 y){
return x.w > y.w;
}
void in(int x, int y, int z){
edge[++tot].v = y;
edge[tot].w = z;
edge[tot].next = st[x];
st[x] = tot;
}
void init(){
for(int i = 1; i <= n; i++) f[i] = i;
memset(fa, 0, sizeof(fa));
memset(g, 0, sizeof(g));
}
int find(int x){
if(f[x] == x) return x;
return f[x] = find(f[x]);
}
void kruskal(){
init();
for(int i = 1, from, to, ff, ft; i <= m; i++){
from = a[i].u, to = a[i].v;
ff = find(from), ft = find(to);
if(ff != ft){
f[ff] = ft;
in(from, to, a[i].w);
in(to, from, a[i].w);
}
}
}
void dfs(int rt){//建树
vis[rt] = 1;
for(int i = 1; i <= 16; i++){
if(deep[rt] < (1<<i)) break;
fa[rt][i] = fa[fa[rt][i-1]][i-1];
g[rt][i] = min(g[rt][i-1], g[fa[rt][i-1]][i-1]);
}
for(int i = st[rt]; i; i = edge[i].next){
int to = edge[i].v;
if(vis[to]) continue;
fa[to][0] = rt;
g[to][0] = edge[i].w;
deep[to] = deep[rt] + 1;
dfs(to);
}
}
int lca(int x, int y){//倍增往上跳,返回最近公共祖先
if(deep[x] < deep[y]) swap(x, y);
int delta = deep[x] - deep[y];
for(int i = 0; i <= 16; i++)//2^16正好过50000
if(delta & (1<<i)) x = fa[x][i];
for(int i = 16; i >= 0; i--)
if(fa[x][i] != fa[y][i]){
x = fa[x][i];
y = fa[y][i];
}
if(x == y) return x;
else return fa[x][0];
}
int ask(int x, int y){//询问路上满足条件的边
int mn = inf;
int delta = deep[x] - deep[y];
for(int i = 0; i <= 16; i++)
if(delta & (1<<i)){
mn = min(mn, g[x][i]);
x = fa[x][i];
}
return mn;
}
int main(){
cin >> n >> m;
for(int i = 1, x, y, z; i <= m; i++)
cin >> a[i].u >> a[i].v >> a[i].w;
sort(a+1, a+m+1, cmp);
kruskal();
for(int i = 1; i <= n; i++)
if(!vis[i]) dfs(i);
cin >> q;
while(q--){
int op, ed;
cin >> op >> ed;
if(find(op) != find(ed)){
cout << "-1" << endl;
continue;
}
else{
int baba = lca(op, ed);
cout << min(ask(op, baba), ask(ed, baba)) << endl;
}
}
return 0;
}
花匠
题目
花匠栋栋种了一排花,每株花都有自己的高度。花儿越长越大,也越来越挤。栋栋决定把这排中的一部分花移走,将剩下的留在原地,使得剩下的花能有空间长大,同时,栋栋希望剩下的花排列得比较别致。
具体而言,栋栋的花的高度可以看成一列整数h1,h2..hn。设当一部分花被移走后,剩下的花的高度依次为g1,g2..gn,则栋栋希望下面两个条件中至少有一个满足:
条件 A:对于所有g(2i)>g(2i-1),g(2i)>g(2i+1)
条件 B:对于所有g(2i)<g(2i-1),g(2i)<g(2i+1) 注意上面两个条件在m="1时同时满足,当m"> 1时最多有一个能满足。
请问,栋栋最多能将多少株花留在原地。
输入输出格式
输入格式:
输入文件为 flower .in。
输入的第一行包含一个整数n,表示开始时花的株数。
第二行包含n个整数,依次为h1,h2..hn,表示每株花的高度。。
输出格式:
输出文件为 flower .out。
输出一行,包含一个整数m,表示最多能留在原地的花的株数。
输入输出样例
样例输入
5
5 3 2 1 2
样例输出
3
说明
样例解释
有多种方法可以正好保留 3 株花,例如,留下第 1、4、5 株,高度分别为 5、1、2,满足条件 B。
数据范围
对于 20%的数据,n ≤ 10;
对于 30%的数据,n ≤ 25;
对于 70%的数据,n ≤ 1000,0 ≤ ℎi≤ 1000;
对于 100%的数据,1 ≤ n ≤ 100,000,0 ≤ hi≤ 1,000,000,所有的hi 随机生成,所有随机数服从某区间内的均匀分布。
思路
意思就是让你找转折点的数量即g(i-1)g(i+1)或g(i-1)>gi<g(i+1),gi即是转折点。首先可以肯定的是,当花的数目为1的时候,可以直接输出1。首尾两株花都肯定是可以留下的,因此,当花的数目大于等于2时,我们可以将答案初始化为2,然后加上转折点的数目即可
代码
#include<iostream>
#include<cstdio>
using namespace std;
int a[100005];
int w,n,ans;
int main(){
scanf("%d",&n);
a[0]=-147258963;
for(int i=1;i<=n;i++){
scanf("%d",&a[++w]);
if(a[w]==a[w-1]) --w;
}
if(w==1) {
printf("1");
return 0;
}
ans=2;
for(int i=2;i<=w-1;i++){
if (((a[i]>a[i-1])&&(a[i]>a[i+1]))||((a[i]<a[i-1])&&(a[i]<a[i+1]))){
ans++;
}
}
printf("%d",ans);
return 0;
}
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可.
发布者: bbqub
转载请注明出处: https://bbqub.cnblogs.com/