2020.8.8【算协集训】[第4次积分赛]

其实这周学的内容不是很会(可能是因为内容越来越难了⑧),还好这次写出来的都是比较擅长/擅长的+找规律的题(?)。越学越觉得时间不够而自己太菜了呀!

Problem A. 胡图图的数学难题

描述
胡图图经常和爷爷讨论一些数学难题,今天爷爷问了图图一个问题难住了他。
爷爷的问题是:现在定义一个数列,数列的前两项均为1,从第三项开始每一项的值都是前两个数开根号之后的和的平方。
数列的通项为 f i = { 1 , 1 ≤ i ≤ 2 ( f i − 2 + f i − 1 ) 2 , i ≥ 3 f_i=\begin{cases}1,\qquad 1≤i≤2 \\ (\sqrt{f_{i-2}} + \sqrt{f_{i-1}})^2,i≥3\end{cases} fi={11i2(fi2 +fi1 )2i3 .
我们需要求这个的数列的前 n项和。因为答案太大,所以输出答案对109 + 7取模的结果即可。
你可以帮图图解决这个难题吗?
输入数据
单组输入 第一行为一个整数n(1 ≤ n ≤ 1018),表示要求的为前n项的和
输出数据
输出一行,一个整数表示答案

样例输入样例输出样例说明
54040 = 1 + 1 + 4 + 9 + 25

分析

这题是究极擅长の矩阵快速幂的题!

其实这道题挺搞人的。首先得判断出这道题是求斐波那契数列的平方的前 n n n 项和,然后还得判断要用矩阵快速幂求斐波那契数列,最后还得思考输出的是啥,由此我们也可以明确这道题的三个小任务。

下面我们来一一探讨一波:

  1. 判断题意
    我判断出这题是斐波那契数列,其实还是用的是土方法——举例子!
    f ( 1 ) = 1 f ( 2 ) = 1 f ( 3 ) = ( 1 + 1 ) 2 = 2 2 = 4 f ( 4 ) = ( 1 + 4 ) 2 = ( 1 + 2 ) 2 = 3 2 = 9 f ( 5 ) = ( 4 + 9 ) 2 = ( 2 + 3 ) 2 = 5 2 = 25 f ( 6 ) = ( 9 + 25 ) 2 = ( 3 + 5 ) 2 = 8 2 = 64 … … f(1)=1 \\ f(2)=1 \\ f(3)=(\sqrt{1}+\sqrt{1})^2=2^2=4 \\ f(4)=(\sqrt{1}+\sqrt{4})^2=(1+2)^2=3^2=9 \\ f(5)=(\sqrt{4}+\sqrt{9})^2=(2+3)^2=5^2=25 \\ f(6)=(\sqrt{9}+\sqrt{25})^2=(3+5)^2=8^2=64 \\ \dots \dots f(1)=1f(2)=1f(3)=(1 +1 )2=22=4f(4)=(1 +4 )2=(1+2)2=32=9f(5)=(4 +9 )2=(2+3)2=52=25f(6)=(9 +25 )2=(3+5)2=82=64
    举出这么多应该能看出来了⑧ ~
    斐波那契数列是 1   1   2   3   5   8   ⋯ ⋯ 1\,1\,2\,3\,5\,8\,\cdots \cdots 112358 ,而这里的 f ( x ) f(x) f(x) 1 2   1 2   2 2   3 2   5 2   8 2   ⋯ ⋯ 1^2\,1^2\,2^2\,3^2\,5^2\,8^2\,\cdots \cdots 121222325282 ,就说明 f ( x ) f(x) f(x) 的含义是斐波那契数列第 x x x 个数的平方。那么题目要求的 f ( x ) f(x) f(x) 的前 n n n 项和,也就是要求斐波那契数列的平方的前 n n n 项和辽~

  2. 确定知识点
    要求斐波那契数列的平方的前 n n n 项和,其实还是立足于求斐波那契数列!
    其实斐波那契数列的求法也有很多,这里举一些栗子:
    (下面的代码都是没加上求模的哈,看个意思就行了)

    1. 递归
      递归求斐波那契数列,应该是大家入门学习编程的启蒙⑧?
      代码大概如下:(当然还有很多写法哈)

      int fib(int x)
      {
      	if(x==1 || x==2)	return 1;
      	return fib(x-1)+fib(x-2);
      }
      

      我们也知道递归会花费很多时间进行重复的运算,这个时候就出现了记忆化搜索的方法——

    2. 记忆化搜索
      记忆化搜索比递归好一点点,是因为它会储存以前算过的值,因此在一开始就可以判断之前有没有算过了,如果算过了就直接返回值,反之则再进行计算。
      代码大概如下:(当然还有很多写法哈)

      const int maxn=1e6+10;
      
      int f[maxn];
      
      int fib(int x)
      {
      	if(f[x])	return f[x];
      	if(x==1 || x==2)	return 1;
      	return f[x]=fib(x-1)+fib(x-2);
      }
      

      但是实际上也可以优化空间,即利用滚动数组——

    3. 滚动数组
      我们可以发现,实际上每次计算的时候只用到了前一个和前前一个的数据。也就是说,我们计算的时候,只用到了前两项的值,更前面的值对于当前要计算的值来说没有直接的意义。因此我们可以利用滚动数组,只开两个空间进行计算,从而达到空间上的优化。
      代码大概如下:(当然还有很多写法哈)

      int fib(int x)
      {
      	if(x==1 || x==2)	return 1;
      	int a,b,c;
      	a=b=1;
      	for(int i=3;i<=x;i++)
      	{
      		c=a+b;
      		a=b;
      		b=c;
      	}
      	return c;
      }
      

      然鹅实际上,我们还有更好的方法,就是用矩阵快速幂——

    4. 矩阵快速幂
      对于斐波那契数列,有 f ( x ) = { 0 , x = 0 1 , 1 ≤ x ≤ 2 f ( x − 2 ) + f ( x − 1 ) , x ≥ 3 f(x)=\begin{cases}0,\qquad x=0\\1,\qquad 1≤x≤2 \\ f(x-2) + f(x-1),x≥3\end{cases} f(x)=0x=011x2f(x2)+f(x1)x3 .因此可以构建式子:
      [ f ( n + 1 ) f ( n ) ] = [ 1 1 1 0 ] [ f ( n ) f ( n − 1 ) ] = [ 1 1 1 0 ] n [ f ( 1 ) f ( 0 ) ] = [ 1 1 1 0 ] n [ 1 0 ] \left[ \begin{matrix} f(n+1) \\ f(n) \end{matrix} \right]= \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right] \left[ \begin{matrix} f(n) \\ f(n-1) \end{matrix} \right]=\left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]^{n} \left[ \begin{matrix} f(1) \\ f(0) \end{matrix} \right]=\left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]^{n} \left[ \begin{matrix} 1 \\ 0 \end{matrix} \right] [f(n+1)f(n)]=[1110][f(n)f(n1)]=[1110]n[f(1)f(0)]=[1110]n[10]
      因此,我们只需要计算 [ 1 1 1 0 ] n \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]^{n} [1110]n 的值,然后再进行最后的输出考虑就星。

  3. 思考输出
    解决了前面两个大问题,又迎来了最后一个问题。因为前面说的计算斐波那契数列,只是我们通往AC路上的一个中转站,根本目的还是要求斐波那契数列的平方的和。而计算出斐波那契数列后,要计算它的平方的和,也有两个思考方向——

    1. 憨憨(我)找规律
      我,一个将憨憨贯彻到底的憨憨,开始坚定地举起了栗子……
      我们可以知道:
      [ 1 1 1 0 ] 1 = [ 1 1 1 0 ] ⇒ 1 \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]^{1}=\left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right] \Rightarrow 1 [1110]1=[1110]1
      [ 1 1 1 0 ] 2 = [ 1 1 1 0 ] ⋅ [ 1 1 1 0 ] = [ 2 1 1 1 ] ⇒ 1 + 1 = 2 \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]^{2}=\left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]·\left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]=\left[ \begin{matrix} 2 & 1 \\ 1 & 1 \end{matrix} \right] \Rightarrow 1+1=2 [1110]2=[1110][1110]=[2111]1+1=2
      [ 1 1 1 0 ] 3 = [ 2 1 1 1 ] ⋅ [ 1 1 1 0 ] = [ 3 2 2 1 ] ⇒ 2 + 4 = 6 \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]^{3}=\left[ \begin{matrix} 2 & 1 \\ 1 & 1 \end{matrix} \right]·\left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]=\left[ \begin{matrix} 3 & 2 \\ 2 & 1 \end{matrix} \right] \Rightarrow 2+4=6 [1110]3=[2111][1110]=[3221]2+4=6
      [ 1 1 1 0 ] 4 = [ 3 2 2 1 ] ⋅ [ 1 1 1 0 ] = [ 5 3 3 2 ] ⇒ 6 + 9 = 15 \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]^{4}=\left[ \begin{matrix} 3 & 2 \\ 2 & 1 \end{matrix} \right]·\left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]=\left[ \begin{matrix} 5 & 3 \\ 3 & 2 \end{matrix} \right] \Rightarrow 6+9=15 [1110]4=[3221][1110]=[5332]6+9=15
      [ 1 1 1 0 ] 5 = [ 5 3 3 2 ] ⋅ [ 1 1 1 0 ] = [ 8 5 5 3 ] ⇒ 15 + 25 = 40 \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]^{5}=\left[ \begin{matrix} 5 & 3 \\ 3 & 2 \end{matrix} \right]·\left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]=\left[ \begin{matrix} 8 & 5 \\ 5 & 3 \end{matrix} \right] \Rightarrow 15+25=40 [1110]5=[5332][1110]=[8553]15+25=40
      ??我现在再看,发现了类似于大佬规律的结论!就是说,如果我们算的结果矩阵的名字是 b b b 的话,我们最终的结果就是 b . m [ 1 ] [ 1 ] ∗ b . m [ 1 ] [ 2 ] b.m[1][1]*b.m[1][2] b.m[1][1]b.m[1][2] 或者是 b . m [ 1 ] [ 1 ] ∗ b . m [ 2 ] [ 1 ] b.m[1][1]*b.m[2][1] b.m[1][1]b.m[2][1] (因为第一行第二列和第二行第一列的值一样)。然鹅刚开始想的时候我蜜汁就看到了最后一列,然后在找最后一列和结果的关系……如果只考虑最后一列,那么我们最终的结果就是 b . m [ 1 ] [ 2 ] ∗ ( b . m [ 1 ] [ 2 ] + b . m [ 2 ] [ 2 ] ) b.m[1][2]*(b.m[1][2]+b.m[2][2]) b.m[1][2](b.m[1][2]+b.m[2][2]) .

    2. 大佬找规律

      菜鸡流下了没用的泪水……

      不过有了大佬的结论,就可以解释 b . m [ 1 ] [ 1 ] ∗ b . m [ 2 ] [ 1 ] b.m[1][1]*b.m[2][1] b.m[1][1]b.m[2][1] 是正确的了。
      因为我们构建的式子是 [ f ( n + 1 ) f ( n ) ] = [ 1 1 1 0 ] n [ 1 0 ] \left[ \begin{matrix} f(n+1) \\ f(n) \end{matrix} \right]=\left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]^{n} \left[ \begin{matrix} 1 \\ 0 \end{matrix} \right] [f(n+1)f(n)]=[1110]n[10] ,算出来了 [ 1 1 1 0 ] n \left[ \begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right]^{n} [1110]n 的值之后,
      可知 f ( n + 1 ) = b . m [ 1 ] [ 1 ] ∗ 1 + b . m [ 1 ] [ 2 ] ∗ 0 = b . m [ 1 ] [ 1 ] f(n+1)=b.m[1][1]*1+b.m[1][2]*0=b.m[1][1] f(n+1)=b.m[1][1]1+b.m[1][2]0=b.m[1][1] f ( n ) = b . m [ 2 ] [ 1 ] ∗ 1 + b . m [ 2 ] [ 2 ] ∗ 0 = b . m [ 2 ] [ 1 ] f(n)=b.m[2][1]*1+b.m[2][2]*0=b.m[2][1] f(n)=b.m[2][1]1+b.m[2][2]0=b.m[2][1]
      然后再根据大佬找出来的规律: ∑ x = 1 n f ( x ) 2 = f ( x ) ∗ f ( x + 1 ) \sum_{x=1}^{n}f(x)^2=f(x)*f(x+1) x=1nf(x)2=f(x)f(x+1) ,结果就是 b . m [ 1 ] [ 1 ] ∗ b . m [ 2 ] [ 1 ] b.m[1][1]*b.m[2][1] b.m[1][1]b.m[2][1] 了。

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int mod=1e9+7;

struct node
{
	ll m[3][3];
};

node mul(node a,node b)	//矩阵相乘
{
	node temp;	//临时矩阵,存放a×b的结果
	memset(temp.m,0,sizeof(temp.m));
	for(int i=1;i<3;i++)
	{
		for(int j=1;j<3;j++)
		{
			for(int k=1;k<3;k++)
			{
				temp.m[i][j]=(temp.m[i][j]+a.m[i][k]*b.m[k][j])%mod;
			}
		}
	}
	return temp;
}

node qpower(node a,ll b)	//快速幂
{
	node temp;
	memset(temp.m,0,sizeof(temp.m));
	for(int i=1;i<3;i++)	//单位矩阵
		temp.m[i][i]=1;
	while(b)
	{
		if(b&1)	temp=mul(temp,a);
		a=mul(a,a);
		b>>=1;
	}
	return temp;	
}

int main()
{
	ll n;
	scanf("%lld",&n);
	node a,b;	//a是最初矩阵,b是结果矩阵
	a.m[1][1]=1,a.m[1][2]=1;
	a.m[2][1]=1,a.m[2][2]=0;
	b=qpower(a,n);
	printf("%lld\n",(b.m[1][2]%mod)*(b.m[1][2]%mod+b.m[2][2]%mod+mod)%mod);
//	printf("%lld\n",(b.m[1][1]%mod)*(b.m[2][1]%mod+mod)%mod);	//另一种方法 
//	printf("%lld\n",(b.m[1][1]%mod)*(b.m[1][2]%mod+mod)%mod);	//另另一种方法 
	return 0;
}

Problem B. 胡图图和小美

描述
胡图图小小年纪就已经成为了番豆幼儿园的手速巨佬。一天,他的好友小美对他说:“我这有一个问题,只要你能答对,我就答应周末和你一起去捉蝴蝶。”,图图兴奋的说:“好!”。图图思考一会后就有了答案,现在请你帮他验证一下他的答案是否正确。问题如下:
番豆幼儿园内摆放了一排石头,其颜色只有红色和白色,现在能以任意次序执行下面两种操作任意次:
1、交换两个石头(可以不相邻)的位置。
2、改变(红变白,白变红)一个石头的颜色。
问:要使红色石头左边没有相邻的白色石头,最少的操作次数是多少?
输入数据
第一行 n,代表石头的个数。(2 ≤ n ≤ 200000)
第二行长度为 n 的字符串,只包含’R’和’W’,分别代表红色和白色的石头。
输出数据
在一行中输出最少的操作次数。

样例输入1样例输出1
4
WWRR
2
样例输入2样例输出2
8
WRWWRWRR
3

分析

题意比较明显哈,就是说有一个只包含 “W” 和 “R” 两个字符的字符串,可以进行两种操作:① 交换任意两个字符的位置;② 改变点的颜色(W变R,R变W)。问最后导致 “W” 的左边没有 “R” 的最少操作次数是多少。其实换句话说,就是把 “R” 放在最左边,“W” 放在最右边的最少操作次数是多少。(随便举几个栗子应该都能体会到,这里就不再举例解释了)

一开始,我们为了让 “R” 放在左边,需要先统计整个字符串中 “R” 的个数,记为 s u m r sumr sumr 。然后我们只需要统计,在原字符串中的 [ 0 , s u m r ) [0,sumr) [0,sumr) 的位置上,有多少 “W” 可以被用来操作。这些 “W” 的个数就是我们最后需要的最少操作次数。
这是因为,最多的操作次数就是原字符串中 “W” 的个数。而我们需要的也只不过是把所有的 “R” 都放在最左边,因此只需要最左边的 s u m r sumr sumr 个位置上都是 “R” ,就已经满足条件了。所以我们只需要看看在最左边 s u m r sumr sumr 个位置上,有多少 “W” ,然后经过某种操作,让 “R” 放在这个位置上,就能够满足条件。因此只需要统计原字符串 [ 0 , s u m r ) [0,sumr) [0,sumr) 的位置上有几个 “W” 就可以了。

(感觉都讲的不太明白,还是举波栗子直观点)

  • 原字符串是 “RWWWWR”
    s u m r = 2 sumr=2 sumr=2。此时在原字符串的 [ 0 , 2 ) [0,2) [0,2) 的位置上,只 1 1 1 “W” 能进行操作(只计算到 [ 0 , 2 ) [0,2) [0,2) ,是因为就要整那么几个 “R” ,算多了也没啥用……)。我们最佳的方案是让最后一个 “R” 与第一个 “W” 交换顺序,变成 “RRWWWW” 。最少操作次数就是 1 1 1 .
  • 原字符串是 “RWWRRR”
    s u m r = 4 sumr=4 sumr=4。此时在原字符串的 [ 0 , 4 ) [0,4) [0,4) 的位置上,只 2 2 2 “W” 能进行操作。这个时候操作①和②都可以。
    对于操作①:让两个 “W” 和最后的两个 “R” 交换顺序,就能变成 “RRRRWW” 。最少操作次数就是 2 2 2 .
    对于操作②:让两个 “W” 直接变成 “R” ,最后就是 “RRRRRR” 。最少操作次数也是 2 2 2 .
    总之,这样的最少操作次数就是 2 2 2 .
  • 原字符串是 “WRRRRW”
    s u m r = 4 sumr=4 sumr=4。此时在原字符串的 [ 0 , 4 ) [0,4) [0,4) 的位置上,仅 1 1 1 “W” 能进行操作。这个时候两种操作也都是可以的,分别变成 “RRRRWW” 或 “RRRRRW” 。最少操作次数就是 1 1 1 .
  • 原字符串是 “WRRWWW”
    s u m r = 2 sumr=2 sumr=2。此时在原字符串的 [ 0 , 2 ) [0,2) [0,2) 的位置上,仅 1 1 1 “W” 能进行操作。这个时候两种操作也都是可以的,分别变成 “RRWWWW” 或 “RRRWWW” 。最少操作次数就是 1 1 1 .
  • ……
    (可以自行多举点栗子体会一波)

可能还是找规律的题?

代码

#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const ll maxn=200010;

int N,sumr,sum;
string str;

int main()
{
	cin>>N;
	cin>>str;
	for(int i=0;i<N;i++)
	{
		if(str[i]=='R')	sumr++;
	}
	for(int i=0;i<sumr;i++)
	{
		if(str[i]=='W')	sum++;
	}
	cout<<sum;
	return 0;
}

Problem C. 胡图图公司大整改

题目描述
胡图图经营了一家公司,最近出现了全员罢工的情况,原因是各个部门的员工抱怨自己的付出得不到相应的薪水,并且嫉妒另一些员工薪水过高。此时,胡图图必须对公司进行薪水大整改。对面如此庞大的公司,诸多部门,海量员工,胡图图想要请你写一个程序来完成胡图图的大整改。公司的部门关系是一个树形结构,一个部门可能会有若干个子部门,公司总部为1号部门。每个部门都有一个薪水标准,胡图图想要对某个部门的薪水标准进行修改,也想要知道在经过若干次修改后,某个部门及其之下的所有部门的最高薪水标准与最低薪水标准之差,便于了解公司的薪水布局。
胡图图公司一共有N个部门,在整改前每个部门都有相应的薪水标准,然后胡图图要进行Q次操作,操作分为以下两类:
操作1:1 x y:胡图图要对x号部门的薪水标准+y
操作2: 2 x:胡图图要查看当前x号部门及其之下的所有部门的最高薪水标准与最低薪水标准之差
输入数据
第一行为数据的组数N Q(1 ⩽ N ⩽ 105,1 ⩽ Q ⩽ 105)
第二行共有N个正整数,第i个正整数w[i]代表第i个部门的薪水标准(1 ⩽ w[i] ⩽ 104)
接下来N-1行,每行为两个数u,v,代表u部门和v部门是直接从属关系(1 ⩽ u,v ⩽ N)
最后Q行,代表胡图图的操作 (1 ⩽ x ⩽ N,−104 ⩽ y ⩽ 104)
输出数据
在Q次操作中,输出操作2的结果

样例输入样例输出
10 10
11 30 14 20 20 1 71 70 42 1
1 2
1 3
2 4
2 5
3 6
3 7
3 8
4 9
4 10
1 1 30
2 10
1 5 -28
2 2
1 5 -12
2 3
1 8 -21
2 3
1 10 50
2 1
0
50
70
70
91
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector> 
using namespace std;
typedef long long ll;
const ll maxn=1e5+10;

ll N,Q,a[maxn],high[maxn],low[maxn],tim;
vector<ll> adj[maxn];

struct node
{
	ll l,r,mx,mi;
}tr[maxn<<2];

void pushup(node &u,node &l,node &r)
{
	u.mx=max(l.mx,r.mx);
	u.mi=min(l.mi,r.mi);
}

void pushup(ll u)
{
	pushup(tr[u],tr[u<<1],tr[u<<1|1]);
}

void build(ll l,ll r,ll u=1)
{
	if(l==r)	tr[u]={l,r};
	else
	{
		ll mid=l+r>>1;
		tr[u]={l,r};
		build(l,mid,u<<1);
		build(mid+1,r,u<<1|1);
	}
}

node query(ll l,ll r,ll u=1)
{
	if(tr[u].l>=l && tr[u].r<=r)	return tr[u];
	else
	{
		ll mid=tr[u].l+tr[u].r>>1;
		if(r<=mid)	return query(l,r,u<<1);
		else if(l>mid)	return query(l,r,u<<1|1);
		else
		{
			auto left=query(l,r,u<<1);
			auto right=query(l,r,u<<1|1);
			node res;
			pushup(res,left,right);
			return res;
		}
	}
}

void modify(ll x,ll c,ll u=1)
{
	if(tr[u].l==x && tr[u].r==x)
	{
		tr[u].mi+=c;
		tr[u].mx+=c;
	}
	else
	{
		ll mid=tr[u].l+tr[u].r>>1;
		if(x<=mid)	modify(x,c,u<<1);
		else	modify(x,c,u<<1|1);
		pushup(u);
	}
}

void dfs(ll u,ll f=-1)
{
	low[u]=++tim;
	for(auto v:adj[u])
	{
		if(v==f)	continue;
		dfs(v,u);
	}
	high[u]=tim;
}

void solve()
{
	for(ll i=1;i<=N;i++)
		scanf("%lld",&a[i]);
	build(1,N);
	ll u,v;
	for(ll i=1;i<=N-1;i++)
	{
		scanf("%lld%lld",&u,&v);
		adj[u].push_back(v) ;
		adj[v].push_back(u);
	}
	dfs(1);
	for(ll i=1;i<=N;i++)
		modify(low[i],a[i]);
	while(Q--)
	{
		int op;
		scanf("%d",&op);
		if(op==1)
		{
			ll x,y;
			scanf("%lld%lld",&x,&y);
			modify(low[x],y);
		}
		else
		{
			ll x;
			scanf("%lld",&x);
			node ans=query(low[x],high[x]);
			printf("%lld\n",ans.mx-ans.mi);
		}
	}
}

int main()
{
	scanf("%lld%lld",&N,&Q);
	solve();
	return 0;
}

Problem D. 胡图图偷吃记

描述
胡图图趁妈妈出去买菜,想要偷吃放在冰箱里的冰淇淋,妈妈早就料到了图图会偷吃,所以在冰箱上设了密码锁,密码锁给定两个数n和m,你需要组成一个长度为n的数组a,使其元素和为m,并使公式p达到最大,则p就是正确答案,麻烦你帮助图图同学解开密码锁好嘛。
关于p的公式为: p = ∑ i = 1 n − 1 ∣ a i − a i + 1 ∣ p = \sum ^{n−1}_{i=1} |a_i −a_{i+1}| p=i=1n1aiai+1
输入数据
第一行输入一个数t(1 ⩽ t ⩽ 10000),表示样例数。
接下来的t行,每行有2个整数,n,m(1 ⩽ n,m ⩽ 109)。
输出数据
输出n行,代表每个样例最大的p。

样例输入样例输出解释说明
4
1 10
2 2
5 5
2 1000000000
0
2
10
1000000000
对于上述样例
第一个样例,只可能组成一个数组10,所以答案是0
第二个样例,可以组成的数组2,0,可以使 p p p 达到最大, p = ∣ 2 − 0 ∣ = 2 p=\vert 2-0 \vert=2 p=20=2
第三个样例,可以组成数组0, 2, 0, 3, 0,使 p p p 达到最大, p = ∣ 0 − 2 ∣ + ∣ 2 − 0 ∣ + ∣ 0 − 3 ∣ + ∣ 3 − 0 ∣ = 10 p=\vert 0−2 \vert+\vert 2−0 \vert+\vert 0−3 \vert+\vert 3−0 \vert=10 p=02+20+03+30=10
第四个样例,组成数组1000000000,0,使p达到最大为1000000000.

分析

这题好像才是真正的签到题,而我这道题因为举的栗子方向错了(还是因为感觉找的规律不对反过来看栗子才发现举的答案错了……),所以还思索了挺久的……(dbq我忏悔)实际上想明白就非常简单。

题意也挺明显的哈:有一个长度为 n n n 、元素和为 m m m 的数组 a a a ,求数组两两元素之差的和最大是多少。

咱们以元素和 m = 5 m=5 m=5 的情况举例:【这里就仅针对这个栗子举(因为这也是我的思考流程),再下面才是总体规律】

  1. n = 1 n=1 n=1
    n = 1 n=1 n=1 时,只能把数组构造成 a = { 5 } a=\{5\} a={5} ,此时 p p p 最大为 p = 0 p=0 p=0 .
  2. n = 2 n=2 n=2
    n = 2 n=2 n=2 时,为了使 p p p 最大,数组就要构造成 a = { 0 , 5 } a=\{0,5\} a={0,5} ,此时 p p p 最大为 p = ∣ 0 − 5 ∣ = 5 p=\vert 0-5 \vert=5 p=05=5 .
  3. n = 3 n=3 n=3
    n = 3 n=3 n=3 时,为了使 p p p 最大,数组就要构造成 a = { 0 , 5 , 0 } a=\{0,5,0\} a={0,5,0} ,此时 p p p 最大为 p = ∣ 0 − 5 ∣ + ∣ 5 − 0 ∣ = 10 p=\vert 0-5 \vert+\vert 5-0 \vert=10 p=05+50=10 .
  4. n = 4 n=4 n=4
    n = 4 n=4 n=4 时,为了使 p p p 最大,数组可以构造成 a = { 0 , 5 , 0 , 0 } a=\{0,5,0,0\} a={0,5,0,0} ,此时 p p p 最大为 p = ∣ 0 − 5 ∣ + ∣ 5 − 0 ∣ + ∣ 0 − 0 ∣ = 10 p=\vert 0-5 \vert+\vert 5-0 \vert+\vert 0-0 \vert=10 p=05+50+00=10 .
  5. n = 5 n=5 n=5
    n = 5 n=5 n=5 时,为了使 p p p 最大,数组可以构造成 a = { 0 , 2 , 0 , 3 , 0 } a=\{0,2,0,3,0\} a={0,2,0,3,0} ,此时 p p p 最大为 p = ∣ 0 − 2 ∣ + ∣ 2 − 0 ∣ + ∣ 0 − 3 ∣ + ∣ 3 − 0 ∣ = 10 p=\vert 0−2 \vert+\vert 2−0 \vert+\vert 0−3 \vert+\vert 3−0 \vert=10 p=02+20+03+30=10 .
  6. n = 6 n=6 n=6
    n = 6 n=6 n=6 时,为了使 p p p 最大,数组可以构造成 a = { 0 , 2 , 0 , 3 , 0 , 0 } a=\{0,2,0,3,0,0\} a={0,2,0,3,0,0} ,此时 p p p 最大为 p = ∣ 0 − 2 ∣ + ∣ 2 − 0 ∣ + ∣ 0 − 3 ∣ + ∣ 3 − 0 ∣ + ∣ 0 − 0 ∣ = 10 p=\vert 0−2 \vert+\vert 2−0 \vert+\vert 0−3 \vert+\vert 3−0 \vert+\vert 0-0 \vert=10 p=02+20+03+30+00=10 .
  7. ……

我们把情况逐一列出后可以发现并总结规律:

  1. n = 1 n=1 n=1
    n = 1 n=1 n=1 时,只能构造出 a = { m } a=\{m\} a={m} ,因此一定是 p = 0 p=0 p=0 .
  2. n = 2 n=2 n=2
    n = 2 n=2 n=2 时,只能构造出 a = { 0 , m } a=\{0,m\} a={0,m} 或是 a = { m , 0 } a=\{m,0\} a={m,0} ,因此一定是 p = m p=m p=m .
  3. n ≥ 3 n≥3 n3
    n ≥ 3 n≥3 n3 时,构造出的格式是 a = { 0 , x , 0 , y , 0 , z , 0 , … … } a=\{0,x,0,y,0,z,0,……\} a={0,x,0,y,0,z,0,} ,其中 x , y , z , ⋯ x,y,z,\cdots x,y,z, 之和为 m m m ,每个数字都可以贡献左右两次,因此一定是 p = 2 ∗ m p=2*m p=2m .

代码

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
const ll maxn=1e6+10;

int T;
ll N,M;

int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%lld%lld",&N,&M);
		if(N==1)
		{
			printf("0\n");
		}
		else if(N==2)
		{
			printf("%lld\n",M);
		}
		else
		{
			printf("%lld\n",2*M);
		}
	}
	return 0;
}

Problem E. 胡图图的难题

描述
胡图图趁着假期和围裙妈妈还有小头爸爸一起出去大冒险,当他们来到了一个漆黑的山洞,这个山洞的出口是一条小路,每次只能通过两人,由于山洞内部很黑所以需要手电筒才可以看清路,但是一起出来冒险的同伴们只有一个手电筒,现在知道了每个人走过去的时间,如果两个人一起走那么就要按照最慢的那个人的速度才可以。为了早点让所有人的通过这个山洞,胡图图找到了你帮帮忙。
输出一个整数表示最短需要的时间是多少。
输入数据
第一行输入一个整数 n (1 ⩽ n ⩽ 1e5)。接下来n个整数表示每个人通过这条路的时间, (1 ⩽ ai ⩽ 1e6)
输出数据
输出一个整数表示最短的花费时间是多少

样例输入样例输出样例解释
3
1 4 7
12先让第一个人和第二个人过去,花费为 4,然后第一个人带着手电筒回来花费时间为 1 ,最后和第三个人一起过去花费时间为 7 因此总共时间为 12

分析

我的天哪兄弟萌!这题就是N人过桥(还是N人过河??)的问题噻!勾起了我玩奥比岛还是摩尔庄园还是赛尔号的回忆……
总之就分析一波:

先接收一波每个人需要花费的时间,然后从小到大排一波序。我们知道,每次走过去返回来的路上花费是依赖于路上花费时间更长的那个的值的。那就有以下最佳的构造方法:

  1. N = 1 N=1 N=1
    如果 N = 1 N=1 N=1 ,说明只有一个人在此岸。要把这个人送到彼岸,只要让他自己拿着手电筒溜溜球就可以了。总花费是 s u m + = a [ 1 ] sum+=a[1] sum+=a[1] .
  2. N = 2 N=2 N=2
    如果 N = 2 N=2 N=2 ,说明有两个人在此岸。要把这两个人送到彼岸,只需要让其中一个人拿着手电筒,两个人一起溜溜球就可以了。总花费是 s u m + = a [ 2 ] sum+=a[2] sum+=a[2] .(过去的时候的值是较长花费的那个人的值)
  3. N = 3 N=3 N=3
    如果 N = 3 N=3 N=3 ,说明有三个人在此岸。因为每次最多让两个人一起走,要把这三个人送到彼岸,得先让花费最少的带花费第二少的走,花费为 a [ 2 ] a[2] a[2] ;花费最少的回来,花费为 a [ 1 ] a[1] a[1] ;花费最少的带花费最多的走,花费为 a [ 3 ] a[3] a[3] 。这样,总花费就是 s u m + = a [ 1 ] + a [ 2 ] + a [ 3 ] sum+=a[1]+a[2]+a[3] sum+=a[1]+a[2]+a[3] .
  4. N ≥ 4 N≥4 N4
    这个情况需要考虑一波。此时有两种细化过后的解决方案:① 让最快的一个把最慢的两个送过桥;② 让最快的两个把最慢的两个送过桥。
    ① :花费最少的先把花费最多的一个送走,花费为 a [ N ] a[N] a[N] ;花费最少的回来,花费为 a [ 1 ] a[1] a[1] ;花费最少的把花费第二多的送走,花费为 a [ N − 1 ] a[N-1] a[N1] ;花费最少的回来,花费为 a [ 1 ] a[1] a[1] 。因此总花费是 s u m + = a [ N ] + a [ N − 1 ] + 2 ∗ a [ 1 ] sum+=a[N]+a[N-1]+2*a[1] sum+=a[N]+a[N1]+2a[1] .
    ② :花费最少的和花费第二少的先到对岸,花费为 a [ 2 ] a[2] a[2] ;花费最少的回来,花费为 a [ 1 ] a[1] a[1] ;花费最多的和花费第二多的走,花费为 a [ N ] a[N] a[N] ,花费第二少的回来,花费为 a [ 2 ] a[2] a[2] 。因此总花费是 s u m + = a [ N ] + a [ 1 ] + 2 ∗ a [ 2 ] sum+=a[N]+a[1]+2*a[2] sum+=a[N]+a[1]+2a[2] .

    判断两个方案选哪一个,只需要比较这两个哪个方案的总花费比较少就可以了,即:
    a [ N ] + a [ N − 1 ] + 2 ∗ a [ 1 ] ≤ a [ N ] + a [ 1 ] + 2 ∗ a [ 2 ] ⇒ a [ 1 ] + a [ N − 1 ] ≤ 2 ∗ a [ 2 ] a[N]+a[N-1]+2*a[1]≤a[N]+a[1]+2*a[2] \Rightarrow a[1]+a[N-1]≤2*a[2] a[N]+a[N1]+2a[1]a[N]+a[1]+2a[2]a[1]+a[N1]2a[2] 时,选方案①。
    a [ N ] + a [ N − 1 ] + 2 ∗ a [ 1 ] > a [ N ] + a [ 1 ] + 2 ∗ a [ 2 ] ⇒ a [ 1 ] + a [ N − 1 ] > 2 ∗ a [ 2 ] a[N]+a[N-1]+2*a[1]>a[N]+a[1]+2*a[2] \Rightarrow a[1]+a[N-1]>2*a[2] a[N]+a[N1]+2a[1]a[N]+a[1]+2a[2]a[1]+a[N1]2a[2] 时,选方案②。
    注意:不管选择哪个方案,最后剩下的人数 N N N 都会减 2 2 2 !减完之后只需要继续递归就可以了。

代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
const ll maxn=1e5+10;

int N,a[maxn];
ll sum;

int main()
{
	scanf("%d",&N);
	for(int i=1;i<=N;i++)
		scanf("%d",&a[i]);
	sort(a+1,a+1+N);
	sum=0;
	while(N>0)
	{
		if(N==1)
		{
			sum+=a[1];
			break;
		}
		else if(N==2)
		{
			sum+=a[2];
			break;
		}
		else if(N==3)
		{
			sum+=a[1]+a[2]+a[3];
			break;
		}
		else
		{
			if(a[1]+a[N-1]<=2*a[2])
			{
				sum+=a[N]+a[N-1]+2*a[1];	//1带N和N-1 
			}
			else
			{
				sum+=a[N]+a[1]+2*a[2];	//1和2走,1回,N和N-1走,2回 
			}
			N-=2;
		}
	}
	printf("%lld\n",sum);
	return 0;
}

Problem F. 胡图图找队友

描述
这个周末胡图图要参加班里举办的拔河比赛,但他还不清楚他的队友是谁,以及谁和谁是一个队伍的,已知队伍一共被分为两队,两边队伍人数可能会不一样。已知他们班一共有n个人,分别用1-n表示,他从老师哪里得到m条信息。这m条信息有两种形式
D a b 表示a和b不在一个队伍
B a b 表示a和b在同一个队伍
胡图图想从这些信息里面判断两个人是否在一个队伍里面,一共进行t组判断
输入数据
第一行是三个整数n(1 ⩽ n ⩽ 103),m(1 ⩽ m ⩽ 103),t(t ⩽ 1000),分别表示班里同学人数,老师给的数据组数,以及他要做t组判断。接下来m行,表示老师给的m条信息。然后有t行,每行两个数字,判断两个人是否为一组
输出数据
一共t行,对于每组判断数据输出一行,如果两个人在一组就输出“Yes”,不在一组就输出“No”,如果根据这些信息无法作出判断,就输出“Not sure”.

#include<cstdio>
typedef long long ll;
const ll maxn=1010;

int N,M,T,u,v;
int fa[maxn<<1];
char ch[2];	//一定要字符数组,不能是单个字符(不知道为啥) 

int findfa(int x)
{
	if(fa[x]!=x)	return fa[x]=findfa(fa[x]);
	return fa[x];
}

void merge(int x,int y)
{
	x=findfa(x);
	y=findfa(y);
	fa[y]=x;
}

bool same(int x,int y)
{
	return findfa(x)==findfa(y);
}

int main()
{
	scanf("%d%d%d",&N,&M,&T);
	for(int i=1;i<=2*N;i++)
		fa[i]=i;
	for(int i=1;i<=M;i++)
	{
		scanf("%s",&ch);
		scanf("%d%d",&u,&v);
		if(ch[0]=='D')
		{
			merge(u+N,v);
			merge(u,v+N);
		}
		else if(ch[0]=='B')
		{
			merge(u,v);
			merge(u+N,v+N);
		}
	}
	while(T--)
	{
		scanf("%d%d",&u,&v);
		if(same(u,v) || same(u+N,v+N))
			printf("Yes\n");
		else if(same(u,v+N) || same(u+N,v))
			printf("No\n");
		else
			printf("Not sure\n");
	}
	return 0;
}

Problem G. 图图的空间站建设

描述
许多年以后,华夏之国的航空航天已空前发达,不知甩了漂亮国几条街,该国的航天工作人员在太空修建了n个空间站。为了纪念地球母亲,将空间站的形状设计为球形,每一个空间站都有四个参数,球心的坐标x,y,z,以及球的半径r,由于华夏之国的基建狂魔的称号不是白盖的,所以一个空间站可以被包含在另一个空间站中,当然也可以和另一个空间站相交。为了使任意两个空间站都能够连通,需要在一些空间站之间修建空间隧道(毕竟太空漫步是一件很危险的事情,搞不好会变成不知道分为哪一类的太空垃圾。显然隧道的成本和隧道的长度是成正比的,为了节约成本,完成这一目标至少要修建多长的隧道?
输入数据
输入包含多组数据,每一组数据以以下形式给出
n
x1 y1 z1 r1
x2 y2 z2 r2

xn yn zn rn

1 <= n <= 100
0 < x,y,z,r <= 100.0
输出数据
输出最小长度,结果保留三位小数

样例输入样例输出
3
10.000 10.000 50.000 10.000
40.000 10.000 50.000 10.000
40.000 40.000 50.000 10.000
2
30.000 30.000 30.000 20.000
40.000 40.000 40.000 20.000
5
5.729 15.143 3.996 25.837
6.013 14.372 4.818 10.671
80.115 63.292 84.477 15.120
64.095 80.924 70.029 14.881
39.472 85.116 71.369 5.553
20.000
0.000
73.834

分析

我以为的签到题……

n n n 个球,给定它们的坐标 ( x , y , z ) (x,y,z) (x,y,z) 和半径 r r r,求连接所有点的最少长度是多少。(两个球可能相交,也可能有包含关系)

其实就是求最小生成树的总长度是多少。对于两个球相交或被包含,可以通过计算两个球心之间的距离,与这两个球的半径之和相比较:

  • 球心距离<两球半径之和
    如果球心距离<两球半径之和,说明两球相交或有包含关系,也就是说不需要建桥就能让两个球连通。此时的建桥长度为 0 0 0
  • 球心距离=两球半径之和
    如果球心距离=两球半径之和,说明两球相切,也就是说不需要建桥就能让两个球连通(这个时候是正好连通)。此时的建桥长度为 0 0 0
  • 球心距离>两球半径之和
    如果球心距离>两球半径之和,说明两球没有交点,也就是说需要建桥连通两个球。而这个桥的长度也没必要是俩球心的距离,因为只需要正好让两个球连通就可以了,即取最小的状态——球心距离 - 两球的半径。

其他的基本就是套最小生成树的模板就可以了。感觉有点类似于 畅通工程再续 (HDU-1875) ——这里是之前写的题解,指路G题。

代码

#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
typedef long long ll;
const ll maxn=110;

int N,tt;
double sum,u,v,w,r;
int fa[maxn];

struct poi	//存放球心的坐标
{
	double x,y,z,r;
}p[maxn];

double dis(int a,int b)	//两球心的距离
{
	return sqrt((p[a].x-p[b].x)*(p[a].x-p[b].x)+(p[a].y-p[b].y)*(p[a].y-p[b].y)+(p[a].z-p[b].z)*(p[a].z-p[b].z));
}

struct node	//结构体存边
{
	int from,to;
	double cost;
	bool operator < (const node & other) const
	{
		return cost<other.cost; 
	}
}e[maxn*maxn];

int findfa(int x)	//寻找父亲节点
{
	if(fa[x]!=x)	return fa[x]=findfa(fa[x]);
	return fa[x];
}

void merge(int x,int y)	//合并
{
	x=findfa(x);
	y=findfa(y);
	fa[y]=x;	//更新父亲节点
}

int main()
{
	while(~scanf("%d",&N))
	{
		for(int i=0;i<maxn;i++)	//初始化父亲节点
			fa[i]=i;
		tt=sum=0;
		for(int i=1;i<=N;i++)
		{
			scanf("%lf %lf %lf %lf",&u,&v,&w,&r);	//存放球心坐标
			p[i].x=u;
			p[i].y=v;
			p[i].z=w;
			p[i].r=r;
		}
		for(int i=1;i<N;i++)
		{
			for(int j=i+1;j<=N;j++)
			{
				double temp=dis(i,j);	//计算两球心距离
				double dis=p[i].r+p[j].r;	//计算两球半径之和
				if(temp>=dis)	temp-=dis;	//两球无交点/相切
				else	temp=0;	//两球已经连通了(不需要建新桥)
				e[tt].from=i;
				e[tt].to=j;
				e[tt++].cost=temp;
			}
		}
		sort(e,e+tt);
		for(int i=0;i<tt;i++)
		{
			int fafrom=findfa(e[i].from);
			int fato=findfa(e[i].to);
			if(fafrom!=fato)
			{
				merge(e[i].from,e[i].to);
				sum+=e[i].cost;	//更新最小生成树上边权之和
			}
		}
		printf("%.3lf\n",sum);	//格式化输出
	}
	return 0;
}

Problem H. 胡图图的好兄弟

描述
一天,胡图图的学长教了胡图图一个算法:
给定两个正整数x y,以及序列a1,a2,…,an,初始时x = 1 y = 0,然后不断的进行以下两个操作:
1.x = x + ax,y = y + ax
2.x = x − ax,y = y + ax
在这个过程中如果出现x ≤ 0 或者 x > n都会在执行完该步操作后结束程序,然后输出此时y的值,如果无法结束程序即陷入循环,程序会自动输出−1.
聪明的胡图图很快就掌握这个简单的算法,于是学长想难为一下这个小学弟。
给出n − 1个序列a2,a3 …,an,胡图图需要给出当a1为i(1 ≤ i ≤ n − 1)时所组成的序列 i,a2,a3,…,an 带入上述算法输出的结果。
胡图图被这个古怪的问题难住了,所以你作为好兄弟要帮助胡图图完成这一个问题。
输入数据
第一行有一个整数n(2 ≤ n ≤ 2·105),下一行有n−1个整数为a2,a3 …,an(1 ≤ ai ≤ 109)
输出数据
输出有n−1行,第i行为上述算法执行序列i,a2,a3,…,an输出的结果。

样例输入1样例输出1Note
4
2 4 1
3
6
8
1.i = 1 序列为1 2 4 1,x: 1 -> 2 -> 0 y: 0 -> 1 -> 3
2.i = 2 序列为2 2 4 1,x: 1 -> 3 -> −1 y: 0 -> 2 -> 6
3.i = 3 序列为3 2 4 1,x: 1 -> 4 -> 3 -> 7 y: 0 -> 3 -> 4 -> 8
样例输入2样例输出2
3 1 2-1
-1
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long ll;
const ll maxn=1e6+10;

int n,arr[maxn];
ll dp[maxn][2];
bool bj[maxn][2];

void dfs(int a,int b)
{
	if(bj[a][b])	return ;
	bj[a][b]=true;
	ll nex;
	if(b&1)
		nex=a-arr[a];
	else
		nex=a+arr[a];
	if(nex<=0 || nex>n)	//下一步是终点
	{
		dp[a][b]=arr[a];
		return ;
	}
	dfs(nex,b^1);
	if(dp[nex][b^1]!=-1)
	{
		dp[a][b]=arr[a]+dp[nex][b^1];	//如果没有循环,就回溯相加 
	}
	return ;
}

int main()
{
	scanf("%d",&n);
	memset(dp,-1,sizeof(dp));
	memset(bj,false,sizeof(bj));
	bj[1][0]=true;
	bj[1][1]=true;
	for(int i=2;i<=n;i++)
		scanf("%lld",&arr[i]);
	for(int i=2;i<=n;i++)
		dfs(i,1);
	for(int i=2;i<=n;i++)
	{
		if(dp[i][1]!=-1)
			dp[i][1]+=i-1;
	}
	for(int i=2;i<=n;i++)
		printf("%lld\n",dp[i][1]);
	return 0;
}

Problem I. 胡图图吃桃子

描述
胡图图很喜欢吃桃子,但是胡图图家里没有桃子。这天,胡图图看到牛爷爷的桃园里面的桃子成熟了,于是就想问牛爷爷要点桃子吃。此时,牛爷爷正在被一个难题困扰,如果胡图图能帮助牛爷爷解决这个难题,牛爷爷就会给胡图图桃子吃。对于一个正整数n,可以找到m个数,这m个数的四次方之和等于n,牛爷爷想找到这个最小的m。(例如:n=706,706 = 54+34,则m=2),你能帮助胡图图吃到桃子吗。
输入数据
第一行输入一个n。0<n<=100,000
输出数据
输出一个整数m。

样例输入样例输出
7062

分析

把一个正整数 n n n 分成 m m m 个数,使得这 m m m 个数的四次方之和等于 n n n ,求最小的 m m m 是多少。

这题我觉得是完全背包的题。类似于“ 有 m m m 个物品,对于物品 i i i ,它的重量是 i 4 i^4 i4 ,价值是 1 1 1 。从这些物品中挑选总重量不大于 n n n 的物品,问最少装几件物品 (或者说是总价值最小是多少)。在这里,每件物品可以挑选任意件 ”。(任意件应该能理解吧?)

想清楚这是完全背包就比较好整了。

先把重量不大于 n n n 的所有物品的重量都计算出来,存放到数组 p p p 中。
m x mx mx 用来计算重量最接近 n n n(但是不大于 n n n )的物品的编号是啥。
f [ i ] f[i] f[i] 存放的是总重量不大于 i i i 的最少的物品数(或是最小的总价值)。因为我们要求的是总价值最小(平常都是求总价值最大),所以我们需要先把数组 f f f 初始化成比较大的值,以免对结果产生干扰。当然 f [ 0 ] f[0] f[0] 的值必定是 0 0 0

然后基本就套一下完全背包的模板就好啦~

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const ll maxn=100010;
const ll INF=0x3f3f3f3f;

int N,p[maxn],mx,f[maxn];

int main()
{
	scanf("%d",&N);
	mx=0;
	for(int i=1;i*i*i*i<=N;i++)
	{
		p[i]=i*i*i*i;
		mx++;
	}
	memset(f,INF,sizeof(f));
	f[0]=0;
	for(int i=1;i<=mx;i++)	//物品编号从1到mx
	{
		for(int j=p[i];j<=N;j++)
		{
			f[j]=min(f[j],f[j-p[i]]+1);
		}
	}
	printf("%d\n",f[N]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值