Noip 2013 真题练习
Day1
T1 转圈游戏
一句话题意:
让你求 \({x + m \times 10^k} \bmod n\) 的结果。
直接套上快速幂的板子。
code
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define LL long long
LL n,m,k,x;
LL ksm(LL a,LL b)
{
LL res = 1;
for(; b; b >>= 1)
{
if(b & 1) res = res * a % n;
a = a * a % n;
}
return res;
}
int main()
{
scanf("%lld%lld%lld%lld",&n,&m,&k,&x);
int tmp = ksm(10,k) * m % n;
printf("%lld\n",(x+tmp)%n);
return 0;
}
T2 火柴排队
先简化问题。给定我们两个序列,我们可以任意交换这两个序列中相邻的两个数。
让你最小化 \(\displaystyle \sum (a[i]-b[i])^2\),的最小交换次数。
首先,一个结论就是 \(A\) 序列中第 \(k\) 大的一定要和 \(B\) 序列中 第 \(k\) 大的数相对应。
这样得到的序列肯定是最小的。
然后,我们就要求这两个序列变化成一一对应的(即上面那种情况)的最小交换次数。
我们可以先求出 \(A\) 序列中的每个数在 \(B\) 序列中对应的数出现的位置,这就可以得到另一个序列 \(C\)。
交换次数就是 \(C\) 的逆序对数(有点绕)。因为你最后肯定是让 \(C\) 序列升序排列。
你也可以把\(C\) 数组理解为要把 \(A\) 中的数变到第几位。
最后,不要忘记离散化一下。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int p = 1e8-3;
int n,ans,tr[100010],q[100010];
struct node
{
int x,id;
}a[100010],b[100010];
inline int read()
{
int s = 0,w = 1; char ch = getchar();
while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
while(ch >= '0' && ch <= '9'){s = s * 10+ch -'0'; ch = getchar();}
return s * w;
}
bool comp(node a,node b)
{
if(a.x == b.x) return a.id < b.id;
return a.x < b.x;
}
int lowbit(int x){return x & (-x);}
void chenge(int x,int val)
{
for(; x <= n; x += lowbit(x)) tr[x] += val;
}
int ask(int x)
{
int res = 0;
for(; x; x -= lowbit(x))
{
res = (res + tr[x]) % p;
}
return res % p;
}
int main()
{
n = read();
for(int i = 1; i <= n; i++)//离散化
{
a[i].x = read();
a[i].id = i;
}
for(int i = 1; i <= n; i++)
{
b[i].x = read();
b[i].id = i;
}
sort(a+1,a+n+1,comp); sort(b+1,b+n+1,comp);
for(int i = 1; i <= n; i++)
{
q[b[i].id] = a[i].id;//第i个大的数所对应的编号
}
for(int i = 1; i <= n; i++)
{
chenge(q[i],1);
int tmp = i - ask(q[i]);
ans = (ans + tmp) % p;
}
printf("%d\n",ans);
return 0;
}
T3 货车运输
好像不用简化问题(大雾)。
这种图论模型,我们不是很好做,所以我们考虑把它转化为我们熟悉的问题(比如
树上问题)。
实际上,当两个点有重边的时候,我们会尽量选边权比较大的边走(很显然吧)。
所以,我们考虑对整个图求一遍最大生成树。这就转化为我们熟悉的树上问题。
求树上路径边权的最小值,直接上树剖套线段树来维护。
如果你这个题不确定这么写是对的,对拍会证明一切
Code:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
int n,m,tot,q,u,v,num;
int head[N],dfn[N],siz[N],top[N],fa[N],f[N],son[N],w[N],dep[N],a[N];
struct node
{
int to,net,w;
}e[N<<1];
struct bian
{
int u,v,w;
}e2[N<<1];
#define l(o) tr[o].lc
#define r(o) tr[o].rc
#define sum(o) tr[o].sum
struct tree
{
int lc,rc,sum;
}tr[N<<2];
void add(int x,int y,int w)
{
e[++tot].w = w;
e[tot].to = y;
e[tot].net = head[x];
head[x] = tot;
}
int find(int x)
{
if(f[x] == x) return x;
else return f[x] = find(f[x]);
}
bool comp(bian a,bian b)
{
return a.w > b.w;
}
------------->//一下是线段树
void up(int o)
{
sum(o) = min(sum(o<<1),sum(o<<1|1));
}
void build(int o,int L,int R)
{
l(o) = L, r(o) = R;
if(L == R)
{
sum(o) = w[L];
return;
}
int mid = (L + R)>>1;
build(o<<1,L,mid);
build(o<<1|1,mid+1,R);
up(o);
}
int ask(int o,int L,int R)
{
int res = 2147483647;
if(L <= l(o) && R >= r(o)) return sum(o);
int mid = (l(o) + r(o))>>1;
if(L <= mid) res = min(res,ask(o<<1,L,R));
if(R > mid) res = min(res,ask(o<<1|1,L,R));
return res;
}
------------>// 以上是线段树
void max_tree(int m)//最大生成树
{
sort(e2+1,e2+m+1,comp);
for(int i = 1; i <= m; i++)
{
int x = e2[i].u, y = e2[i].v, w = e2[i].w;
int fx = find(x);
int fy = find(y);
if(fx == fy) continue;
f[fx] = fy;
add(x,y,w); add(y,x,w);
}
}
void get_tree(int x)
{
dep[x] = dep[fa[x]] + 1; siz[x] = 1;
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(to == fa[x] || dep[to]) continue;
fa[to] = x; a[to] = e[i].w;
get_tree(to);
siz[x] += siz[to];
if(siz[to] > siz[son[x]]) son[x] = to;
}
}
-------->// 树剖
void dfs(int x,int topp)
{
top[x] = topp; dfn[x] = ++num; w[dfn[x]] = a[x];
if(son[x]) dfs(son[x],topp);
for(int i = head[x]; i; i = e[i].net)
{
int to = e[i].to;
if(dfn[to]) continue;
dfs(to,to);
}
}
int query(int x,int y)
{
int ans = 2147483647;
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x,y);
ans = min(ans,ask(1,dfn[top[x]],dfn[x]));
x = fa[top[x]];
}
if(dep[x] > dep[y]) swap(x,y);
ans = min(ans,ask(1,dfn[x]+1,dfn[y]));
return ans;
}
---------->//
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++) f[i] = i;
for(int i = 1; i <= m; i++)
{
scanf("%d%d%d",&e2[i].u,&e2[i].v,&e2[i].w);
}
max_tree(m); get_tree(1);//预处理
dfs(1,1); build(1,1,n);
scanf("%d",&q);
for(int i = 1; i <= q; i++)
{
scanf("%d%d",&u,&v);
if(find(u) != find(v)) printf("%d\n",-1);//两个点之间不连通,就是无解情况
else printf("%d\n",query(u,v));
}
return 0;
}
Day2
T1 积木大赛
好像18年又出了和这个一模一样的原题。双倍经验
我们考虑当这个序列单调上升的时候,从 \(i\) 到 \(i+1\),我们只需要操作 \(a_{i+1} - a_i\) 次,因为你前面可以连带着
把这个也铺了。因此我们每个点对答案的贡献就是 \(a[i] - max(a[i],a[i-1])\) .
这个可以怎么理解呢?如果这个比前面的高度要小,我们可以在铺前面的时候把他也给铺了,比前面高度大的时候,
我们只需要在往上填几次就够了。
Code
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,a[100010],ans,maxn;
int main()
{
scanf("%d",&n);
for(int i = 1; i <= n; i++) scanf("%d",&a[i]);
for(int i = 1; i <= n; i++)
{
if(a[i] > maxn)
{
ans += a[i] - maxn;
maxn = a[i];
}
else maxn = a[i];
}
printf("%d\n",ans);
return 0;
}
T2 花匠
一句话题意:给定一个序列,让你求把这个序列变成中间这个数比旁边两个数低或高的最小操作次数,
也就是波浪形的(可这样真的好看吗)
我们设 \(f[i][0]\) 表示到 \(i\) 时该下降时能选的最大的花的数量,\(f[i][1]\) 则表示 到\(i\) 时该上升时,能选的花的最大数量。
我们很容易能想出 O(\(n^2\)) 的dp
\(f[i][0] = max(f[i][0],f[j][1] + 1)\) ,\(f[i][1] = max(f[i][1],f[j][0]+1)\)
然后随便搞一下线段树优化 \(dp\) 就可以搞成 O(\(nlogn\)) 的啦。
可这样写代码比较长,并且比较难调。
我们考虑一下\(O(n)\) 的\(dp\)
- \(h[i] > h[i-1]\) 则 \(f[i][0] = f[i-1][1] + 1\) , 表示我们可以选这盆花。
- 则 \(f[i][1] = f[i-1][1]\) 表示我们可以让它成为新的波峰,来承接更多的花。
- \(h[i] < h[i-1]\) ,则 \(f[i][1] = f[i-1][0] + 1\) , 同理表示我们选这盆花
- 那么 \(f[i][0] = f[i-1][0]\) 即我们让他成为新的波谷,来承接更多的花。
Code:
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,ans,h[2000010],f[200001][2];
int main()
{
scanf("%d",&n);
for(int i = 1; i <= n; i++) scanf("%d",&h[i]);
f[1][0] = f[1][1] = 1;
for(int i = 2; i <= n; i++)
{
if(h[i] > h[i-1])
{
f[i][0] = f[i-1][1] + 1;
}
else f[i][0] = f[i-1][0];//成为新的波谷
if(h[i] < h[i-1])
{
f[i][1] = f[i-1][0] + 1;//可以选上这盆花
}
else f[i][1] = f[i-1][1];//成为新的波峰
}
printf("%d\n",max(f[n][0],f[n][1]));
}
T3 华容道
太毒瘤了,不想写,先咕着吧。