寒假训练3
问题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
思路
这道题就是考察快速幂.
代码
#include<cstdio>
int Powmod(int a,int b,int MO)
{
a%=MO;
int ret=1;
while(b)
{
if(b&1)
ret=1LL*ret*a%MO;
b>>=1;
a=1LL*a*a%MO;
}
return ret;
}
int main()
{
int n;
scanf("%d",&n);
int ans=Powmod(n,n,10);
printf("%d\n",ans);
return 0;
}
问题B:蜥蜴和地下室 (51Nod - 1489)
哈利喜欢玩角色扮演的电脑游戏《蜥蜴和地下室》。
如果哈利用他的火球咒语攻击第i个弓箭手(他们从左到右标记),这个弓箭手会失去a点生命值。
同时,这个咒语使与第i个弓箭手左右相邻的弓箭手(如果存在)分别失去b(1 ≤ b < a ≤ 10)点生命值。
但是哈利能用他的火球攻击其他任何弓箭手。
每个弓箭手的生命值都已知。当一个弓箭手的生命值小于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
思路
最开始想的是类似于滑窗的思路,但是仔细想了一下是错误的.
后来看题解说可以用dfs暴力做.也就是先把最左边和最右边的弓箭手射死。然后直接从第二个弓箭手进去,保证在pos的时候把pos-1位置的弓箭手射死。那么要先保证弓箭手pos-1的血量必须小于0,并且只能由pos位置的火球所击杀。但是pos位置的弓箭手可以被pos位置的火球射死,也可以被pos+1位置的火球射死。现在只考虑被pos位置的火球射死,那么对它释放法术的上限为hp[pos]/a+1,于是我们便有了枚举的范围,也就是t+1<=i<=hp[pos]/a+1,那么就可以继续dfs下去了。
代码
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MAXN 10
#define INF 0x3FFFFFFF
using namespace std;
typedef long long LL;
int n;
int h[MAXN+5];
LL ans=INF;
int a,b;
void dfs(int pos,LL cnt)
{
if(pos==n)
{
ans=min(ans,cnt);
return;
}
if(h[pos-1]<0)
dfs(pos+1,cnt);
LL t=0;
if(h[pos-1]>=0)
{
t=h[pos-1]/b+1;
h[pos-1]-=t*b;
h[pos]-=t*a;
h[pos+1]-=t*b;
dfs(pos+1,cnt+t);
h[pos-1]+=t*b;
h[pos]+=t*a;
h[pos+1]+=t*b;
}
LL t1=0;
if(h[pos]>=0)
t1=h[pos]/a+1;
if(h[pos]>=0&&t1>t)
{
for(LL i=t+1;i<=t1;i++)
{
h[pos-1]-=i*b;
h[pos]-=i*a;
h[pos+1]-=i*b;
dfs(pos+1,cnt+i);
h[pos-1]+=i*b;
h[pos]+=i*a;
h[pos+1]+=i*b;
}
}
}
int main()
{
scanf("%d %d %d",&n,&a,&b);
for(int i=1;i<=n;i++)
scanf("%d",&h[i]);
LL t1=0LL,t2=0LL;
if(h[1]>=0)
{
t1=h[1]/b+1;
h[1]-=t1*b;
h[2]-=t1*a;
h[3]-=t1*b;
}
if(h[n]>=0)
{
t2=h[n]/b+1;
h[n]-=t2*b;
h[n-1]-=t2*a;
h[n-2]-=t2*b;
}
dfs(2,1LL*t1+1LL*t2);
if(ans<INF)
printf("%lld\n",ans);
else
printf("%lld\n",t1+t2);
return 0;
}
问题C:前缀后缀集合 (51Nod - 1280)
一个数组包含N个正整数,其中有些是重复的。
换句话说前缀的集合A0..P0..P与后缀集合AS..N−1S..N−1包含完全相同的值。
求这样的前缀后缀集合的数量。
(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
思路
首先我们需要一个逆序之后的数组b,原数组a,以及两个map:A、B,A中存的是a从前往后已经出现过的数字;B中存的是b从前往后已经出现的并且与A中的数字已经匹配(即A中存在)。对于b来说,我们定义一个beg(初值为1)作为每次从b开始枚举的位置。在外层枚举a,从中取出一个数放在A中,然后再从beg开始枚举b,如果b[j]在A中存在,就把它放在B中,然后再检验如果A.size()等于B.size(),那么就是我们要的其中一个答案,res++.如果b[j]在A中不存在,那么就退出了。如果我们既在A中找到了b[j],之前又已经找到了一个答案,那么b[j]就与之前的某个值重复了,直接让res++继续即可。退出这个内层循环后,ans+=res;
在刚刚进循环的时候,如果我们取出的a[i]已经在A中出现过了,此时的res还是存的是上一次的值,就让ans+=res,继续即可。
#include<cstdio>
#include<map>
#define MAXN 50000
using namespace std;
map<int,int> A;
map<int,int> B;
int a[MAXN+5];
int b[MAXN+5];
int main()
{
int n,beg=1;
long long ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
b[n-i+1]=a[i];
}
long long res=0LL;
for(int i=1;i<=n;i++)
{
map<int,int>::iterator it=A.find(a[i]);
if(it!=A.end())
{
ans+=res;
continue;
}
A[a[i]]=1;
res=0LL;
bool Find=false;
for(int j=beg;j<=n;j++)
{
it=A.find(b[j]);
if(it==A.end())
break;
if(Find)
{
res++;
continue;
}
B[b[j]]=1;
if(A.size()==B.size())
{
Find=true;
res++;
beg=j;
}
}
ans+=1LL*res;
}
printf("%lld\n",ans);
return 0;
}
问题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
思路
首先,对于k来说,从小到大,当k足够小时,可以放很多旗子,但是会不够用,此时旗子的数量成为了可以放的旗子的数量的限制,那么k越大,答案就越大;当k比较大时,有很多旗子可以用,但是由于距离限制,用不能放那么多,也就是k越大,答案却越小。那么我们要的最大值就是中间那个峰值。若ans小于lastans就可以退出,而lastans就是最终答案。
那么,对于每一个k来说,根据贪心的思想,最左边的那个山峰是必定要放旗子的,因为这样能够为右边空出最大的空间。然后右边的合法山峰就形成了一个子问题。当然,最开始的时候我们需要把山峰的坐标存下来。
代码
#include<cstdio>
#include<vector>
#include<cstring>
#define MAXN 100000
using namespace std;
int h[MAXN+5];
vector<int> Top;
int n;
int solve(int k)
{
if(Top.empty()==true)
return 0;
int lastpos=Top.front();
int ret=1;
for(int i=1;i<Top.size();i++)
{
if(Top[i]-lastpos>=k)
lastpos=Top[i],ret++;
}
return ret;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&h[i]);
for(int i=2;i<=n-1;i++)
if(i-1>=1&&i+1<=n&&h[i]>h[i-1]&&h[i]>h[i+1])
Top.push_back(i);
int last=0;
for(int k=1;k<=n+1;k++)
{
int ans=solve(k);
ans=min(ans,k);
if(ans<last)
break;
if(ans==0)
break;
last=ans;
}
printf("%d\n",last);
return 0;
}
问题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
思路
数据所给出的数组是可以变换顺序的,并不一定要完全按照给出的顺序来排等差数列。
用DP来做。我们定义DP[i][j]为以a[i]开头,以a[j]为第二个数的等差数列的最大值。我们固定位置j,让i从j-1的位置向前枚举,让k从j+1的位置向后枚举,如果a[k]-a[j]==a[j]-a[i]那么当前的a[i],a[j],a[k]就可以组成一个等差数列的开始部分,那么就有dp[i][j]=max(dp[i][j],dp[j][k]+1)。注意,j需要从后往前枚举,以保证dp[j][k]是已经求出来了的。dp需要全部初始化为2。而且dp由于下标过大,为了防止炸内存,可以用short int来存。
代码
#include<cstdio>
#include<algorithm>
#define MAXN 10000
using namespace std;
short int dp[MAXN+5][MAXN+5];
int a[MAXN+5];
int main()
{
int n,ans=2;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
sort(a+1,a+1+n);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
dp[i][j]=2;
for(int j=n-1;j>=2;j--)
{
int i=j-1,k=j+1;
while(i>=1&&k<=n)
{
if(a[j]-a[i]<a[k]-a[j])
i--;
else if(a[j]-a[i]>a[k]-a[j])
k++;
else
{
dp[i][j]=max(dp[i][j],(short int)(dp[j][k]+1));
ans=max(dp[i][j],(short int)ans);
i--,k++;
}
}
}
printf("%d\n",ans);
return 0;
}
问题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
思路
定义dp[u][0]表示u节点执行+1操作的次数,dp[u][1]表示u节点执行-1操作的次数。贪心的想,一次操作应该选取尽量多的点,那么在必要的情况下,对于当前的点的操作应该与它下面的点所需要的操作视作同一次操作,这样就是最优的。于是就有了如下的DP转移式。
先让dp[u][0]=max{dp[v][0]},dp[u][1]=max{dp[v][1]}。最后再看val[u]+dp[u][0]-dp[u][1]是否不等于0。如果大于0,就让dp[u][1]+=val[u]+dp[u][0]-dp[u][1];如果小于0,就让dp[u][0]+=-(val[u]+dp[u][0]-dp[u][1]),即dp[u][0]-=val[u]+dp[u][0]-dp[u][1].
代码
#include<cstdio>
#include<algorithm>
#define MAXN 100000
using namespace std;
typedef long long LL;
struct node
{
int to;
node *next;
}edge[2*MAXN+5];
node *ncnt=&edge[0],*Adj[MAXN+5];
int n;
LL num[MAXN+5];
LL dp[MAXN+5][2];
void AddEdge(int u,int v)
{
node *p=++ncnt;
p->to=v;
p->next=Adj[u];
Adj[u]=p;
node *q=++ncnt;
q->to=u;
q->next=Adj[v];
Adj[v]=q;
}
void dfs(int u,int fa)
{
for(node *p=Adj[u];p!=NULL;p=p->next)
{
int v=p->to;
if(v!=fa)
{
dfs(v,u);
dp[u][0]=max(dp[u][0],dp[v][0]);
dp[u][1]=max(dp[u][1],dp[v][1]);
}
}
num[u]+=1LL*dp[u][0]-1LL*dp[u][1];
if(num[u]>0) dp[u][1]+=1LL*num[u];
if(num[u]<0) dp[u][0]-=1LL*num[u];
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n-1;i++)
{
int u,v;
scanf("%d %d",&u,&v);
AddEdge(u,v);
}
for(int i=1;i<=n;i++)
scanf("%lld",&num[i]);
dfs(1,-1);
printf("%lld\n",1LL*dp[1][0]+1LL*dp[1][1]);
return 0;
}
问题G:Jabby’s segment tree (51Nod - 1792)
待后续跟进……