G题没时间做了,之前想了个要炸的O(NQ),后来想了个没想好的O(nlogn+Qlogn);
A - n^n的末位数字 51Nod - 1004
给出一个整数N,输出N^N(N的N次方)的十进制表示的末位数字。
Input
一个数N(1 <= N <= 10^9)
Output
输出N^N的末位数字
Sample Input
13
Sample Output
3
思路
一上来就是道水题,快速幂模10不就完了吗。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
int ksm(int x,int y,int MOD)
{
int k=1;
x%=MOD;
while(y>0)
{
if(y&1)
k=(k*x)%MOD;
x=(x*x)%MOD;
y>>=1;
}
return k;
}
int main()
{
int n;
scanf("%d",&n);
printf("%d\n",ksm(n,n,10));
}
B - 蜥蜴和地下室 51Nod - 1489
哈利喜欢玩角色扮演的电脑游戏《蜥蜴和地下室》。此时,他正在扮演一个魔术师。在最后一关,他必须和一排的弓箭手战斗。他唯一能消灭他们的办法是一个火球咒语。如果哈利用他的火球咒语攻击第i个弓箭手(他们从左到右标记),这个弓箭手会失去a点生命值。同时,这个咒语使与第i个弓箭手左右相邻的弓箭手(如果存在)分别失去b(1 ≤ b < a ≤ 10)点生命值。
因为两个端点的弓箭手(即标记为1和n的弓箭手)与你相隔较远,所以火球不能直接攻击他们。但是哈利能用他的火球攻击其他任何弓箭手。
每个弓箭手的生命值都已知。当一个弓箭手的生命值小于0时,这个弓箭手会死亡。请求出哈利杀死所有的敌人所需使用的最少的火球数。
如果弓箭手已经死亡,哈利仍旧可以将他的火球扔向这个弓箭手。
Input
第一行包含3个整数 n, a, b (3 ≤ n ≤ 10; 1 ≤ b < a ≤ 10),第二行包含n个整数――h1,h2,…,hn (1 ≤ hi ≤ 15), hi 是第i个弓箭手所拥有的生命力。
Output
以一行输出t――所需要的最少的火球数。
Sample Input
3 2 1
2 2 2
Sample Output
3
思路
开始以为是贪心,因为边边上的只能有一种打死方式,于是写了个贪心,理所应当的wa了。这才发现中间的可以有几种方式打死,再一看数据范围,写了个十分暴力的dfs,枚举当前是否被打死,如果没有,就枚举两种打死方式。结果wa了,其实这个dfs和上面的贪心都有一个bug。没有考虑两种被打方式共存的情况。于是写了个更加暴力的dfs。枚举打的次数。从刚好能把前面的打死到能把自己打死。没有卡时间就过了。(两种打死指的是用a和b)(dfs前先把两边打死)
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=15;
int h[MAXN],n,a,b,ans=0,ee=0;
void dfs(int x,int jans)
{
if(x>=n)
{
ans=min(ans,jans);
return ;
}
if(h[x-1]<0)
{
dfs(x+1,jans);
}
int c=0;
if(h[x-1]>0)
{
c=h[x-1]/b+1;
h[x-1]-=b*c;
h[x]-=a*c;
h[x+1]-=b*c;
dfs(x+1,jans+c);
h[x-1]+=b*c;
h[x]+=a*c;
h[x+1]+=b*c;
}
int c2=h[x]/a+1;
if(h[x]>=0)
{
for(int i=c+1;i<=c2;i++)
{
h[x-1]-=b*i;
h[x]-=a*i;
h[x+1]-=b*i;
dfs(x+1,jans+i);
h[x-1]+=b*i;
h[x]+=a*i;
h[x+1]+=b*i;
}
}
}
int main()
{
scanf("%d %d %d",&n,&a,&b);
for(int i=1;i<=n;i++)
{
scanf("%d",&h[i]);
}
if(h[1]>=0)
{
int c=h[1]/b+1;
ee+=c;
h[1]-=b*c;
h[2]-=a*c;
h[3]-=b*c;
}
if(h[n]>=0)
{
int c=h[n]/b+1;
ee+=c;
h[n-2]-=b*c;
h[n-1]-=a*c;
h[n]-=b*c;
}
ans=1000000;
dfs(2,ee);
printf("%d\n",ans);
}
C - 前缀后缀集合 51Nod - 1280
一个数组包含N个正整数,其中有些是重复的。一个前缀后缀集是满足这样条件的下标对(P,S), 0<= P,S < N 满足数组元素A0..P0..P的值也在AS..N−1S..N−1的值中出现,并且AS..N−1S..N−1中的值也再A0..P0..P中出现。换句话说前缀的集合A0..P0..P与后缀集合AS..N−1S..N−1包含完全相同的值。求这样的前缀后缀集合的数量。
例如:3 5 7 3 3 5,共有14个集合符合条件:(1, 4), (1, 3), (2, 2), (2, 1), (2, 0), (3, 2), (3, 1), (3, 0), (4, 2), (4, 1), (4, 0), (5, 2), (5, 1), (5, 0)
本题由 @javaman 翻译。
Input
第1行:一个数N, 表示数组的长度(1 <= N <= 50000)。
第2 - N + 1行:每行1个数,对应数组中的元素Ai。(1 <= Ai <= 10^9)
Output
输出符合条件的集合数量。
Sample Input
6
3
5
7
3
3
5
Sample Output
14
思路
开始看到这道题一头雾水O(n^2)(就是暴力枚举S,P)过不了啊,。于是去做后面的题。等到再回来看时,便想把暴力的O(n^2)进行优化,首先离散化,这样就可以用vis打标记了(其实map也行,不过我喜欢离散化),然后再看,如果枚举到某P时枚举S时S枚举着枚举着,咔嚓,S有P没有的了,那么就可以break了,但是这不够,P每次只是加了一个元数(而且还可能没有加,如果没有加就直接加上次的答案咯),上次的vis如果清零就太可惜了,应该可以利用一下,很明显,能给答案有贡献的应该是一段连续的区间,当上次成功有贡献后,上次的区间明显和这次要找到只差了一个元素,我们可以从上次失败的地方继续找,这样后面的时间复杂的就成功的变成了O(n)加上离散化,为O(nlogn);
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=50005;
int n,a[MAXN],b[MAXN];
bool vis[MAXN],vis2[MAXN];
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b,b+n);
int tot=unique(b,b+n)-b;
for(int i=0;i<n;i++)
{
a[i]=lower_bound(b,b+tot,a[i])-b;
//printf("%d ",a[i]);
}
int x=0,y=0,ans=0,jans=0,s=n-1;
for(int i=0;i<n;i++)
{
if(!vis[a[i]])
{
vis[a[i]]=1;
x++;
}
else
{
ans+=jans;
continue;
}
jans=0;
for(int j=s;j>=0;j--)
{
if(!vis[a[j]])
break;
if(!vis2[a[j]])
{
vis2[a[j]]=1;
y++;
}
if(x==y)
{
jans++;
s=j-1;
}
}
ans+=jans;
}
printf("%d\n",ans);
}
D - 山峰和旗子 51Nod - 1281
用一个长度为N的整数数组A,描述山峰和山谷的高度。山峰需要满足如下条件, 0 < P < N - 1 且 AP−1P−1 < APP > AP+1P+1。
现在要在山峰上插上K个旗子,并且每个旗子之间的距离 >= K,问最多能插上多少个旗子(即求K的最大值)。两个山峰之间的距离为|P - Q|。
以上图为例,高度为:1 5 3 4 3 4 1 2 3 4 6 2。其中可以作为山峰的点为:1 3 5 10。
放2面旗子, 可以放在1 和 5。
放3面旗子, 可以放在1 5 和 10。
放4面旗子, 可以放在1 5 和 10,之后就放不下了。
所以最多可以放3面旗子。
Input
第1行:一个数N,表示数组的长度(1 <= N <= 50000)。
第2 - N + 1行:每行1个数Ai(1 <= Ai <= 10^9)。
Output
输出最多能插上多少面旗子(即求K的最大值)。
Sample Input
12
1
5
3
4
3
4
1
2
3
4
6
2
Sample Output
3
思路
这个先处理一下变成只有山峰,存的是山峰的距离。然后发现这道题和原来做的某道奶牛跳石头有惊人的相似,于是便想到用二分。二分答案,然后check;过了;
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=50005;
int a[MAXN],b[MAXN],tot=0,n;
bool check(int x)
{
int k=0,la=-1000;
for(int i=1;i<=tot;i++)
{
if(b[i]-la>=x)
{
la=b[i];
k++;
}
}
return k>=x;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=2;i<n;i++)
{
if(a[i-1]<a[i]&&a[i]>a[i+1])
{
b[++tot]=i;
}
}
int l=0,r=n;
while(r-l>1)
{
int mid=(l+r)>>1;
if(check(mid))
l=mid;
else
r=mid;
}
printf("%d\n",l);
}
E - 最长等差数列 51Nod - 1055
N个不同的正整数,找出由这些数组成的最长的等差数列。
例如:1 3 5 6 8 9 10 12 13 14
等差子数列包括(仅包括两项的不列举)
1 3 5
1 5 9 13
3 6 9 12
3 8 13
5 9 13
6 8 10 12 14
其中6 8 10 12 14最长,长度为5。
Input
第1行:N,N为正整数的数量(3 <= N <= 10000)。
第2 - N+1行:N个正整数。(2<= Aii <= 10^9)
Output
最长等差数列的长度。
Sample Input
10
1
3
5
6
8
9
10
12
13
14
Sample Output
5
思路
开始读题读半天,没太懂,以为是要找一个等差的子串,一脸懵逼。便去做F题。倒回来做时才发现是“由这些数组成的最长的等差数列”,不用讲究顺序。这样肯定先排序呀。然后想贪心,贪心不对,那应该是DP,先想dp[i][j] 表示第i个数为末尾,数列差为j的最大等差长度,但是很明显。存不下啊。于是变成dp[i][j]表示第i个数为末尾上一个数为j的最大等差长度,这样空间小了很多,也可以知道差了。但是每次要找i,j和另外一个数的话就是O(n^3)了,假设i是中间的那个数,那么前面的数l和后面的r必须和i构成等差数列才能计算,如果每次确定l后都找一遍r的话,就算是logn也O(n^2logn)太慢了,受c题影响,我发现完全没有这个必要,如果l从i-1开始向前走,那么由于数列是排了序的,l要找的数一定在上次找到的数的后面,如果相等,那么看那边移动的越少移哪边,这样就只有O(n^2)了。时间开了2s,10000应该能勉强卡过吧。但是————MLE!哇,内存超了一倍怎么办!无可奈何,我向李兽骏东(化名)询问有没有坑,李兽骏东说:“反正我用short int过了”;short int!!!,我**,这个东西说实话,我以前没有怎么用过,现在我还真的差点忘了有这个东西,而且ans还不会炸。真是个好东西,我把dp改成了short int 过了。。。。。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const int MAXN=10005;
short int dp[MAXN][MAXN];
int a[MAXN];
int n,ans=0;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
fill(dp[i],dp[i]+MAXN,2);
}
ans=2;
sort(a+1,a+n+1);
for(int i=2;i<n;i++)
{
int l=i-1,r=i+1;
while(l>=1&&r<=n)
{
if(a[i]-a[l]==a[r]-a[i])
{
dp[r][i]=dp[r][i]<dp[i][l]+1?dp[i][l]+1:dp[r][i];
ans=ans<dp[r][i]?dp[r][i]:ans;
if(l==1||a[i]-a[l-1]>a[r+1]-a[i])
r++;
else
l--;
}
if(a[i]-a[l]>a[r]-a[i])
r++;
if(a[i]-a[l]<a[r]-a[i])
l--;
}
}
printf("%d\n",ans);
}
F - 零树 51Nod - 1424
有一棵以1为根的树,他有n个结点,用1到n编号。第i号点有一个值vi。
现在可以对树进行如下操作:
步骤1:在树中选一个连通块,这个连通块必须包含1这个结点。
步骤2:然后对这个连通块中所有结点的值加1或者减1。
问最少要经过几次操作才能把树中所有结点都变成0。
注意:步骤1与步骤2合在一起为一次操作。
Input
单组测试数据。
第一行有一个整数n(1 ≤ n ≤ 10^5)
接下来n-1行,每行给出 ai 和 bi (1 ≤ ai, bi ≤ n; ai ≠ bi),表示ai和bi之间有一条边,输入保证是一棵树。
最后一行有n个以空格分开的整数,表示n个结点的值v1, v2, …, vn (|vi| ≤ 10^9)。
Output
输出一个整数表示最少的操作步数。.
Sample Input
3
1 2
1 3
1 -1 1
Sample Output
3
思路
这个题开始还在想用拓扑序做的,后来码到一半发现完全没必要,直接dfs记录儿子的正负使用情况(就是加了多少次,减了多少次),然后这个节点的情况就是所有儿子的最大值再加上自己(自己要先加上改变的情况)。交上去,wa。看数据范围,要用long long,正式的比赛要是出了这个问题那真是太严重了,每道题还是看看数据范围。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
const long long MAXN=100005;
long long add[MAXN],ua[MAXN],val[MAXN],n;
vector<int>w[MAXN];
void dfs(long long u,long long fa)
{
for(long long i=0;i<int(w[u].size());i++)
{
long long v=w[u][i];
if(v!=fa)
{
dfs(v,u);
add[u]=max(add[u],add[v]);
ua[u]=max(ua[u],ua[v]);
}
}
val[u]=val[u]+add[u]-ua[u];
if(val[u]>0)
ua[u]+=val[u];
if(val[u]<0)
add[u]-=val[u];
}
int main()
{
scanf("%I64d",&n);
for(long long i=1;i<n;i++)
{
long long u,v;
scanf("%I64d %I64d",&u,&v);
w[u].push_back(v);
w[v].push_back(u);
}
for(long long i=1;i<=n;i++)
scanf("%I64d",&val[i]);
dfs(1,-1);
printf("%I64d\n",add[1]+ua[1]);
}
G - Jabby’s segment tree 51Nod - 1792
线段树是一种经典的数据结构,一颗1,n1,n的线段树他的根是1,n1,n,当一个线段树的结点是l,rl,r时,设mid=(l+r)>>1,则这个结点的左儿子右儿子分别是l,midl,mid,mid+1,rmid+1,r
当我们在线段树上跑x,yx,y询问时,一般是从根节点开始计算的,设现在所在结点是l,rl,r,有以下几种分支:
1.若x,yx,y包含l,rl,r,计算结束
2.否则,若左儿子和x,yx,y有交,计算左儿子,若右儿子和x,yx,y有交,计算右儿子
定义询问x,yx,y的费用是询问时计算了几个结点
给定Q次询问,每次给定l,r,求满足l<=x<=y<=r的(x,y)的费用之和
你需要将答案对1000000007取模
Input
第一行两个正整数n,Q(1<=n,Q<=100000)
接下来Q行每行两个正整数l,r,保证l<=r
Output
输出Q行,每行一个非负整数表示答案
Sample Input
2 1
1 2
Sample Output
5
思路
本次考试没有做出来的题,看了题解,打算明天再码。
代码
waiting