学长@ACMLCZH所出的一套Noip模拟赛题,难度适中,涵盖范围较广,考察了一些比较基础的试题。个人还是感觉不错的。
T1小红帽的画笔(artist)
Description
小红帽是 Pop star 上最著名的人类画家,她可以将任何画出的东西变成真实的物品。赋予她这样神奇能力的正是她手上的画笔。
小红帽每次作画时,都需要用到她的调色盘,我们把每个自然数都对应一种颜色,那么小红帽的调色盘就可以看成是一个斐波那契数列(数列第 1、 2 项都为 1), 小红帽每次需要一种颜色时,她都会用画笔蘸取一段区间,得到的颜色就是区间里所有的数之和。
受到秋之国人民的邀请,小红帽要为他们画一个夏天。 小红帽要进行 n 次取色,给出每次蘸取的区间[l,r],作为小 C 委派来进行记录的你需要输出每次小红帽得到的颜色, 答案对 mod 取模。
Input
第一行是两个正整数 n、 mod,表示询问的次数和模数。
接下来 n 行,每行两个正整数 l, r,表示每蘸取的颜色区间。
Output
输出共 n 行,对于每次取色,输出题目所述的得到的颜色,答案对 mod 取模。
Sample Input
3 5
1 2
2 4
4 8
Sample Output
2
1
0
Hint
对于 10%的数据,$ n leq 100,l,r leq 10^4 $ ;
对于 30%的数据,$ l,r leq 10^7 $ ;
对于 90%的数据,(mod leq 10^9);
对于 100%的数据,(0leq n leq 1000,1 leq l leq r leq 10^{18},0 < mod leq 10^{18})。
Solution
显然我们这是一道矩阵快速幂题,我们通过推导很容易构造出这样一个转移矩阵:$$
egin{pmatrix}
0 & 1 & 1
1 & 1 & 0
0 & 0 & 1
end{pmatrix} $$
乘上这么一个初始矩阵$$egin{pmatrix} 1 & 1 & 0 end{pmatrix}$$ 就可以得到我们想要的结果.
接下来考虑求区间和,就直接矩阵快速幂求解即可.
然而这样仅能够通过90%的测试点,对于100%的数据,我们需要化乘为加,使用分治思想就可以优化效率至(log_{2} Ans)计算了。
总时间复杂度为(O(n 3^3(log_{2} {l*r})*log_2 Ans))
Code
#include <stdio.h>
#include <algorithm>
#include <string.h>
#define r register
#define Filename "artist"
#define ll long long
inline ll read(){
r ll x; r bool f; r char c;
for (f=0; (c=getchar())<'0'||c>'9'; f=c=='-');
for (x=c-'0'; (c=getchar())>='0'&&c<='9'; x=(x<<3)+(x<<1)+c-'0');
return f?-x:x;
}
ll mod,ans;
inline ll mult(ll a,ll b){
if (a<b) std::swap(a,b); r ll z=0;
for (;b;b>>=1,a<<=1,a>=mod?a-=mod:a) if (b&1) z+=a,z>=mod?z-=mod:z;
return z;
}
struct mat{
ll a[3][3];
inline friend mat operator *(const mat &a,const mat &b){
mat c;memset(c.a,0,sizeof(c.a));
for (r int i=0; i<3; ++i)
for (r int j=0; j<3; ++j)
for (r int k=0; k<3; ++k)
c.a[i][j]+=mult(a.a[i][k],b.a[k][j]),c.a[i][j]>=mod?c.a[i][j]-=mod:c.a[i][j];
return c;
}
}fir,sd,mov;
inline mat pow(mat a,ll k){
mat ans=sd;
for (;k;k>>=1,a=a*a) if (k&1) ans=ans*a;
return ans;
}
void init(){
freopen(Filename".in","r",stdin);freopen(Filename".out","w",stdout);
for (r int i=0; i<3; ++i) sd.a[i][i]=1;fir.a[0][0]=fir.a[0][1]=1;
mov.a[0][1]=mov.a[0][2]=mov.a[1][0]=mov.a[1][1]=mov.a[2][2]=1;
}
void solve(){
r int n=read();mod=read();for (r int i=1; i<=n; ++i){
r ll l=read(),rt=read();
if (mod==1) {puts("0"); continue;}
r mat anss; anss=fir*pow(mov,l-1); ans=anss.a[0][2];
anss=fir*pow(mov,rt); ans=anss.a[0][2]-ans;
ans<0?ans+=mod:ans;printf("%lld
",ans);
}
}
int main(){init(); solve(); return 0;}
T2灰姑娘的水晶鞋(crystal)
Description
传说中的水晶鞋有两种颜色: 左边的水晶鞋是红色,右边的是蓝色,据说穿上它们会有神奇的力量。
灰姑娘要找到她所有的 n 双水晶鞋,它们散落在一条数轴的正半轴上, 坐标各不相同,每双水晶鞋还有一个权值 wi。灰姑娘一开始位于原点,当她将其中一双水晶鞋拿回原点时, 那双水晶鞋就改变了它原来的位置,这时还固定于数轴正半轴上的那些水晶鞋就会因为共鸣而发生升华现象。升华过程的损耗为: 被取走的水晶鞋发生的位移*(还固定在数轴正半轴上的 编号与被取走的水晶鞋编号对 m 取模同余 的水晶鞋的 w 权值之和)。
现在灰姑娘想得到所有的水晶鞋,但是她又希望升华的消耗最少,因为她希望能在秋之国即将举办的夏日祭里赢得更多的掌声。所以她找到了你帮忙。
Input
第一行为两个正整数 n、 m,意义同题目中所述。
接下来 n 行,每行 2 个正整数, 分别为第 i 双(即编号为 i 的)水晶鞋的坐标,和权值 wi。
Output
一个正整数,表示最少的升华消耗。
Sample Input
4 1
1 2
5 4
3 1
6 1
Sample Output
19
Hint
对于 25%的数据, (n leq 10) ;
对于另外 5%的数据,$ m=n $ ;
对于 60%的数据,$ n leq 1000 $ ;
对于另外 10%的数据,$ m=1 $ ;
对于 100%的数据,$ 0 < n,m leq 200,000, 0<坐标范围 leq 10^9 ,0 < ai,bi leq 10^4 $ 。样例数据说明
首先拿取第一双水晶鞋,消耗为 1(4+1+1)=6;
再拿取第二双水晶鞋,消耗为 5(1+1)=10;
再拿取第三双水晶鞋,消耗为 3*(1)=3;
拿第四双水晶鞋没有消耗,总消耗为 6+10+3=19。
Solution
我们考虑m=1的情况:
显然对于任意2个水晶鞋,我们假设第i双先取会优于第j双,那么我们有[d[i]*(W-w[i])+d[j]*(W-w[i]-w[j]) < d[j]*(W-w[j])+d[i]*(W-w[j]-w[i]) ][Rightarrow d[i]*w[j]<d[j]*w[i] ][Rightarrow frac{d[i]}{w[i]} < frac {d[j]}{w[j]} ]根据上面这个式子,显然我们可以发现按照斜率排序后即可确认去物品的顺序。
但这仅仅是m=1的情况,容易发现对于一组同余的水晶鞋,它们是相互独立的,故可以使用上式分组解决,于是就解决了此题.
时间效率(O(nlog_{2} n))
Code
#include <stdio.h>
#include <algorithm>
#define r register
#define ll long long
#define MN 200005
#define inf 1000000000000000000ll
#define Filename "crystal"
inline int read(){
r int x; r bool f; r char c;
for (f=0; (c=getchar())<'0'||c>'9'; f=c=='-');
for (x=c-'0'; (c=getchar())>='0'&&c<='9'; x=(x<<3)+(x<<1)+c-'0');
return f?-x:x;
}
ll ans1,ans2;int n,m,d[MN],w[MN],a[MN];
inline void ad(ll x){ans2+=x;if (ans2>inf) ++ans1,ans2-=inf;}
inline bool cmp(int x,int y){return 1ll*d[x]*w[y]<1ll*d[y]*w[x];}
int main(){
freopen(Filename".in","r",stdin);
freopen(Filename".out","w",stdout);n=read(),m=read();
for (r int i=1; i<=n; ++i) d[i]=read(),w[i]=read();
for (r int i=1; i<=m; ++i){
r int cnt,sum,j;
for (cnt=sum=0,j=i; j<=n; j+=m) a[++cnt]=j,sum+=w[j];
std::sort(a+1,a+cnt+1,cmp);
for (j=1; j<=cnt; ++j) sum-=w[a[j]],ad(1ll*sum*d[a[j]]);
}if (ans1) printf("%lld%018lld",ans1,ans2); else printf("%lld",ans2);
}
T3魔法使的烟花(mushroom)
Description
魔法森林里有很多蘑菇, 魔法使常常采摘它们来制作魔法药水。
为了在 6 月的那个奇妙的晚上用魔法绽放出最绚丽的烟花,魔法使决定对魔法森林进行一番彻底的勘探。
魔法森林分为 n 个区域,由 n-1 条长度相等的道路将它们连接为了无向连通图。每个区域都有一个等级,这 n 个区域的等级满足如下性质:
①1 号区域的等级为 1;
②由一条道路连接的两个区域(即相邻两个区域)等级相差为 1;
③设两个区域 x,y 被一条道路连接,若 x 到 1 号区域的最短距离小于 y 到 1 号区域的最短距离,则有 x 到 1 号区域的最短路径上的所有区域的等级必定小于 y 的等级。
由于魔法森林各个区域的气候、地形、水文的不同,每个区域都有一个精华值,其中第 i 号区域的精华值为 wi。 整片魔法森林还有一个共鸣度 m。每个区域吸收的精华值为一个包含该区域的连通子图的精华值之和,由于共鸣度限制, 能被一个区域吸收精华值的所有区域的等级必须比该区域的等级高 x, x 为不超过 k 的自然数。
魔法使希望得到每个区域所吸收的精华值,这将有助于她规划蘑菇的采摘,于是她把这个任务交给了你,因为她还要去住在这附近的另一位魔法使家中喝杯茶。
Input
第一行为两个整数 n、 m,表示魔法森林的区域数和共鸣度。
第二行为 n 个正整数,第 i 个数表示第 i 号区域的精华值 wi。
接下来 n-1 行,每行两个正整数,表示一条道路连接的两个区域。
Output
输出共 n 行,第 i 行表示第 i 号区域吸收的精华值。
Sample Input
6 1
3 1 4 1 5 9
3 1
1 2
2 4
2 5
5 6
Sample Output
8
7
4
1
14
9
Hint
对于 20%的数据,$n,m leq 1000 $ ;
对于 70%的数据,$n,m leq 10^5 $ ;
对于另外 10%的数据, (m=n) ;
对于 100%的数据, $0 leq n,m leq 10^6 ,0<wi leq 1000 $。
Solution
题意就是:给定一棵树,求每个节点以它为根的子树中,距离与它不超过m的节点权值和。
考虑树上差分,用一个桶来存储在当前遍历的路径上各深度的节点,然后树上差分乱搞即可。
Code
#include <stdio.h>
#define MN 1000005
#define r register
#define Filename "mushroom"
inline int read(){
r int x; r bool f; r char c;
for (f=0; (c=getchar())<'0'||c>'9'; f=c=='-');
for (x=c-'0'; (c=getchar())>='0'&&c<='9'; x=(x<<3)+(x<<1)+c-'0');
return f?-x:x;
}
int w[MN],fir[MN],head[MN],dep,nxt[MN<<1],to[MN<<1],cf[MN],cnt,n,m;
inline void ins(int x,int y){to[++cnt]=y,nxt[cnt]=head[x],head[x]=cnt;}
void dfs(int x,int fa){
r int y=dep-m; if (y<0) y=0;
cf[fir[y]]-=w[x];cf[x]+=w[x];fir[++dep]=x;
for (r int i=head[x]; i; i=nxt[i])
if (to[i]!=fa) dfs(to[i],x),cf[x]+=cf[to[i]];--dep;
}
int main(){
freopen(Filename".in","r",stdin);
freopen(Filename".out","w",stdout);n=read(),m=read();
for (r int i=1; i<=n; ++i) w[i]=read();
for (r int i=1; i<n; ++i){
r int x=read(),y=read();
ins(x,y);ins(y,x);
}dfs(1,0);for (r int i=1; i<=n; ++i) printf("%d
",cf[i]);
}
T4战斗机的祈雨仪式(lightning)
Description
炎炎夏日,如果没有一场大雨怎么才能尽兴?秋之国的人民准备了一场祈雨仪式。战斗机由于拥有操纵雷电的能力,所以也加入了其中,为此,她进行了一番准备。
战斗机需要给自己的Spear of Longinus灌注法力。Longinus的长度为n,战斗机为它制作了一个长度相等的灌注槽,因此这个灌注槽就被等分为了n段,每一段有两个增益值ai、bi。战斗机可以对每一段指定一个灌注的法力类型,每一段都有两种类型(A、B)可选,战斗机需要在灌注法力前确定好这n段所灌注的法力类型。由于仪式的需要,其中有m1段要强制选择A类法力,m2段要强制选择B类法力。
确定完每段所灌注的法力类型时,接下来就是法力灌注的过程。n段灌注槽中分别灌满了代表该段所选定法力类型的结晶,且一开始每段的法术强度都为0。每次战斗机需要合并相邻的两段成为新的一段,新的一段结晶的法术强度除了包括两段结晶的法术强度总和以外,还两段结晶两端处的结晶的法力类型有关,如果两段结晶左(右)端的法力类型相异,那么合并后的结晶的法术强度还要加上两段结晶左(右)端的a增益值之积;如果两段结晶左(右)端的法力类型相同,那么合并后的结晶的法术强度还要加上两段结晶左(右)端的b增益值之积。举个例子:
灌注槽的长度为n,所以一开始有n段结晶。战斗机要把它们合并成一整段结晶为Longinus灌注法术,她希望为祈雨仪式增光添彩,所以她希望找到一个灌注法力和合并结晶的方案,使Spear of Longinus被灌注的法术强度尽可能大。
Input
第一行为两个整数n、m1、m2,表示灌注槽长度、法力类型为A的段数和法力类型为B的段数。
第二行为m1个正整数,表示法力类型为A的段的编号{αi}。
第三行为m2个正整数,表示法力类型为B的段的编号{βi}。
接下来n行,每行两个正整数,表示每段的增益值ai,bi。
Output
一个整数,表示最大法术强度。
Sample Input
6 2 3
1 3
2 4 5
1 3
2 3
1 5
2 2
3 3
7 1
Sample Output
102
Hint
对于10%的数据,$n leq 10 $ ;
对于50%的数据,$ m1,m2 leq 5 $ ;
对于另外20%的数据,$m1+m2=n $ ;
对于100%的数据,$ 0leq m1,m2 leq 200,0<alpha i,eta i leq n leq 200,0 < ai,bi leq 1000 $ ,保证 $alpha i,eta i $ 各不相同。样例数据说明
n 个格子灌注的法力类型分别为 ABABBA。
合并[1,1]和[2,2], [1,2]的法术强度为 0+0+12+12=4;
合并[5,5]和[6,6], [5,6]的法术强度为 0+0+37+37=42;
合并[4,4]和[5,6], [4,6]的法术强度为 0+42+23+27=62;
合并[3,3]和[4,6], [3,6]的法术强度为 0+62+12+51=69;
合并[1,2]和[3,6], [1,6]的法术强度为 4+69+35+27=102。
Solution
考虑m1+m2=n的情况,显然用区间DP (O(n^3))乱搞即可。
当m1+m2<n时,我们考虑增加DP的状态数:同时保存左右端点的颜色,然后still是一个简单的区间DP乱搞即可......
注意一下题目的限制条件与区间DP的边界。时间效率为(O(16n^3)).
Code
#include <stdio.h>
#define MN 205
#define r register
#define inf 0x3f3f3f3f
#define Filename "lightning"
#define max(a,b) ((a)>(b)?(a):(b))
inline int read(){
r int x; r bool f; r char c;
for (f=0; (c=getchar())<'0'||c>'9'; f=c=='-');
for (x=c-'0'; (c=getchar())>='0'&&c<='9'; x=(x<<3)+(x<<1)+c-'0');
return f?-x:x;
}
int n,m1,m2,f[MN][MN][2][2],col[MN],w[2][MN];
void init(){
freopen(Filename".in","r",stdin);
freopen(Filename".out","w",stdout);
n=read(),m1=read(),m2=read();
for (r int i=1; i<=n; ++i) col[i]=2;
for (r int i=1; i<=m1; ++i) col[read()]=1;
for (r int i=1; i<=m2; ++i) col[read()]=0;
for (r int i=1; i<=n; ++i) w[1][i]=read(),w[0][i]=read(),f[i][i][0][1]=f[i][i][1][0]=-inf;
for (r int i=1; i<=n; ++i)
for (r int j=i; j<=n; ++j){
if (col[i]<2) f[i][j][col[i]^1][0]=f[i][j][col[i]^1][1]=-inf;
if (col[j]<2) f[i][j][0][col[j]^1]=f[i][j][1][col[j]^1]=-inf;
}
}
void dp(){
for (r int i=1; i<n; ++i)
for (r int lt=1,rt=i+1; rt<=n; ++lt,++rt)
for (r int c1=0; c1<2; ++c1)
for (r int c2=0; c2<2; ++c2)
if (f[lt][rt][c1][c2]!=-inf)
for (r int k=lt; k<rt; ++k)
for (r int c3=0; c3<2; ++c3)
for (r int c4=0; c4<2; ++c4)
f[lt][rt][c1][c2]=max(f[lt][k][c1][c3]+f[k+1][rt][c4][c2]+w[c1^c4][lt]*w[c1^c4][k+1]+w[c2^c3][k]*w[c2^c3][rt],f[lt][rt][c1][c2]);
r int ans=-inf;for (r int c1=0; c1<2; ++c1)
for (r int c2=0; c2<2; ++c2) ans=max(ans,f[1][n][c1][c2]);printf("%d",ans);
}
int main(){init(); dp(); return 0;}