链接
http://codeforces.com/contest/1485
A. Add and Divide
(rating:1000)
题意:
给两个数a,b;可以进行如下两个操作:
1) a = [a/b] (除法,下取整)
2) b = b+1;
要求用最小的操作次数使a = 0;
思路:
当b=1时,必须先+1;
算出只做操作1,也就是a一直除以b的情况,得到一个操作数ans。
由于时间复杂度允许,那么最简单的处理方法就是遍历 b —> b+ans ,取最小操作数。
B. Replace and Keep Sorted
(rating:1200)
题意:
给一段长度为n的递增数组 a1 , a2 , a3 … an 和 一个上界k 。
再给一个询问次数 q,每次询问给定a数组的一个区间 [l,r] , 要求统计与给定区间满足k-similar的总数组个数。
对于构造出的数组,规定如果它与数组a的[l,r]区间满足以下4点:
1)满足严格单增 。
2)长度相同
3)所有的数都在1~k之间
4)有且仅有1处不同。
就称之为k-silimar数组。
思路:
一开始写根本没看数据范围导致T了一发23333。 (n,q <= 1e5 )
由于只能改变一处,且为了满足单调递增 , 那么我们枚举数组a 在 [l,r]上的每一个元素 , 每个元素可以替换成 a[i-1]~a[i+1] 之间的任何值,求下和即可 。
但是由于时间复杂度限制,我们不能去枚举区间每个点,否则时间是O(n*q)
既然要o(n)解决 , 那么做下预处理 。 sum[i]表示区间[1,i]的k-similar个数。
每次询问时,对于区间[l,r] ,如果改变的位置是 [l+1,r-1] , 那么总个数就是sum[r-1] - sum[l] 。
如果改变的是端点l,r,那么显然,个数为(a[l]-1)+(k-a[r])+(a[l+1]-a[l]-1)+(a[r]-a[r-1]-1)) 。
代码段:
while(q--){
ll l,r;
scanf("%lld %lld",&l,&r);
ll cnt = 0;
if(l==r){
printf("%lld\n",k-1);
}
else if(l+1==r){
printf("%lld\n",k-2+a[r]-a[l]-1);
}
else{
printf("%lld\n",sum[r-1]-sum[l]+(a[l]-1)+(k-a[r])+(a[l+1]-a[l]-1)+(a[r]-a[r-1]-1));
}
}
C. Floor and Mod(数论+分块问题)
(rating:1700)
题意:
给两个数x,y . 找出所有满足1<=a<=x , 1<=b<=y,使得[a/b] = a mod b 的个数 .
即 a除以b的商与余数相同 。 其中 x,y<=109
思路:
例如 , 当 b = 6且x很大时,显然满足条件的a有:
a = 7 , a =14 , a = 21,a = 28, a = 35 这5个 。
从这里我们可以发现几个问题:
1) 满足条件的a之间相差 b+1 .
2)对于b , 满足条件的a最多有 b-1个
3) 当 x >= b*b - 1 时 , 这b-1个都可以取到 。
那么,首先找到一个最大的b,使得b-1个都可以取到 。 (就叫mid吧)
总数 ans += 1+2+3+4+ … + mid-1 ;
对于mid < b < y的这些值 , 是无法取完 b-1 个的,从上面的问题1可以知道,此时的b可以取 [x/(b+1)]个 。 但是我们也不能一个一个地去遍历求每个mid<b<y的[x/(b+1)] 。 但是,对于mid<b<y来讲 , 总有一个连续的区间 [bi,bj] , 它们可以取到的a的个数是相同的 。
我们发现 , 对于 “能取到cnt个a” 的b ,最大的b就是取第cnt个时的a恰好离上界x最近的情况 。 也就是last = [x / cnt] - 1 ; 因此,当我们找到 “能取到cnt个a” 的第一个b时,就可以通过这样直接跳到“能取到cnt个a” 的最后一个b 。 ans += cnt * (last -first +1).
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main(){
int t;
cin>>t;
while(t--){
ll a,b;
cin>>a>>b;
a = min(a,b*b);
b = min(a,b);
ll mid = 0;
for(ll i=1;i<=b;i++){
if((ll)i*i-(ll)1>a)
break;
else
mid = i;
}
ll ans = 0;
ans += (mid)*(mid-1)/2;
mid++;
while(mid<=b){
ll cnt = a/(mid+1);
if(cnt==0) break;
ll last = min(a/cnt-1,b);
ans += cnt*(last-mid+1);
mid = last+1;
}
cout<<ans<<endl;
}
}
D. Multiples and Power Differences(构造)
(rating:2200)
题意:
给一个n行m列的矩阵a (aij <= 16) , 要求构造出一个矩阵b,满足以下条件:
1)大小与a相同
2)bij是aij的倍数
3)b中每一对相邻元素之差的绝对值是某个数的四次方 。
思路:
构造的关键在于aij <= 16 :
我们可以取 lcm(1,2,3, … ,16 ) ,这个数等于 720720 ,保证bij一定是aij的倍数 。
对于相邻元素 , 必然有横纵坐标之和(即 i+j) 相差1。
那么,只需要令 当i +j 为奇数时 , bij = 720720; 当i+j 为偶数时 , bij = 720720 + a[i][j]4 即可 。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll gcd(ll a,ll b){
return b==0?a:gcd(b,a%b);
}
int main(){
ll ans = 1;
ll now = 16;
for(int i=15;i>=1;i--){
now = i*now / gcd(i,now);
}
ll n,m;
cin>>n>>m;
ll x = 0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>x;
if((i+j)%2==0)
cout<<now<<" ";
else
cout<<now+x*x*x*x<<" ";
}
cout<<endl;
}
}
E. Move and Swap(树形DP)
(rating:2500)
题意:
给一棵根为1的树 , 树上每个节点都有一个权值 。
现在有红蓝两枚硬币从根节点开始往下跳 。
每一个步骤,红硬币只可以跳到当前节点的任意孩子节点 , 而蓝硬币可以跳到深度+1的任意节点。 这个步骤的得分 = | 红硬币所在节点的权值 - 蓝硬币所在节点的权值 | 。 当前步骤完成后 , 你可以选择交换红蓝硬币的位置(也可以不交换) 。 问最终得分的最大值是多少。
思路:
挺难的树形dp ,借着题解补了一下午 。
如果不能交换红蓝硬币的话题目会简单一些:
dp[u] = max(dp[v]) + max(abs(a[u] - a[u0])) , 因为蓝硬币没有限制 . 其中v是u的子节点 , u0是与u同深度的任意节点 ,下面的v0同理 ,是与v同一深度的任意节点 。
那么,如果此时让红蓝交换,就可以有:
dp[u] = max(dp[v0]) +max(abs(a[u] - a[u0])) 。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll maxn = 2e5+5;
struct A{
ll val; //节点权值
ll id; //节点编号
ll deep; //节点深度
}a[maxn];
struct E{
ll to;
ll nxt;
}e[maxn<<1];
ll head[maxn<<1],tot;
ll add_edge(ll u,ll v){
e[++tot].to = v;
e[tot].nxt = head[u];
head[u] = tot;
}
ll max_son[maxn]; // i节点的权值最大儿子
ll max_deep[maxn]; // i深度的权值最大点
ll min_deep[maxn]; // i深度的权值最小点
ll fa[maxn];
ll dp[maxn];
//dfs预处理 节点的深度和每个深度最大,最小的权值
void dfs(ll u,ll f){
fa[u] = f;
if(max_deep[a[u].deep]<a[u].val)
max_deep[a[u].deep] = a[u].val;
if(min_deep[a[u].deep]>a[u].val)
min_deep[a[u].deep] = a[u].val;
for(int i=head[u];i;i=e[i].nxt){
int v = e[i].to;
if(v==f) continue;
a[v].deep = a[u].deep + 1;
dfs(v,u);
}
}
//按深度给节点排序
bool cmp(A x,A y){
return x.deep>y.deep;
}
//初始化
void clear(ll n){
tot = 0;
for(int i=0;i<=n;i++)
head[i] = 0;
}
int main(){
int t;
cin>>t;
while(t--){
ll n;
ll maxx = -1e17,minn = -1e17;
scanf("%lld",&n);
//输入并初始化
for(int i=2;i<=n;i++){
ll u,v = i;
scanf("%lld",&u);
add_edge(u,v);
add_edge(v,u);
}
a[1].id = 1;a[1].val = 0;a[1].deep = 1;dp[1] = 0;max_deep[1] = 0;min_deep[1] = 1e9+7;max_son[1] = 0;
for(int i=2;i<=n;i++){
scanf("%lld",&a[i].val);
a[i].id = i;
a[i].deep = 1;
min_deep[i] = 1e9+7 , max_deep[i] = 0 , max_son[i] = 0 , dp[i] = 0 , fa[i] = 0;
}
//预处理
dfs(1,0);
//按深度排序 ,从大到小 , dp【1】即结果
sort(a+1,a+n+1,cmp);
ll now = 1;
for(ll i=1;i<=n;i++){
if(a[i].deep!=a[i-1].deep && i!=1){
for(now;now<i;now++){
dp[a[now].id] = max(dp[a[now].id],maxx-a[now].val);
dp[a[now].id] = max(dp[a[now].id],minn+a[now].val);
}
now = i,maxx = minn = -1e17;
}
ll u = a[i].id;
max_son[u] = 0; //max_son 当前节点dp值最大的孩子节点
for(int z=head[u];z;z=e[z].nxt){
int v=e[z].to;
if(v==fa[u]) continue;
max_son[u]=max(max_son[u],dp[v]);
}
ll dep = a[i].deep;
// 当前节点的dp值 = 最大孩子的dp值 + max(当前深度节点权值绝对值)
dp[u]=max_son[u]+max(max_deep[dep]-a[i].val,a[i].val-min_deep[dep]);
maxx=max(max_son[u]+a[i].val,maxx);
minn=max(max_son[u]-a[i].val,minn);
}
printf("%lld\n",dp[1]);
clear(n);
}
}