2022JMU蓝桥国赛模拟赛

发布时间:2023-11-21 19:30

文章目录

    • 走马
      • 题目大意
      • 解题思路
    • 抉择吧 在苦与痛的深渊里
      • 题目大意
      • 解题
      • 代码
    • 美化字符串
      • 题目大意
      • 解题思路
    • 分糖果
      • 题目大意
      • 解题思路
      • 代码实现
    • 快递站
      • 题目大意
      • 解题
      • 代码
    • 汤神的积木
      • 题目大意
      • 解题
      • 代码
    • 两极反转
      • 题目大意
      • 解题思路
      • 代码实现
    • 互不攻击的车
      • 题目大意
      • 解题
      • 代码
    • 白嫖才最香
      • 题目大意
      • 解题
      • 代码
    • Pot
      • 题目大意
      • 解题
      • 代码

走马

出题人:贝

题目大意

如果对于中国象棋中的“马”,放在坐标系中的 ( 1 , 1 ) (1,1) (1,1)位置,限制马只能朝着右上方走(即任何时刻仅有两种走法)。

例如马处于 ( 1 , 1 ) (1,1) (1,1)时,马的一步只能沿 x x x轴正方向增加 1 1 1同时沿 y y y轴正方向增加 2 2 2走到 ( 2 , 3 ) (2,3) (2,3),或沿 x x x轴正方向增加 2 2 2同时沿 y y y轴正方向增加 1 1 1走到 ( 3 , 2 ) (3,2) (3,2)
问走到 ( 12 , 11 ) (12,11) (12,11)这个坐标有多少条不同路径?

解题思路

我们设dp[i][j]为从(1, 1)走到(i, j)的路径数量,显然dp[i][j]可以走一步,然后转移到dp[i + 1][j + 2]dp[i + 2][j + 1]中,在dp[i + 1][j + 2]dp[i + 2][j + 1]中累加上dp[i][j]的数值即完成转移。因为我们初始位于dp[1][1],且仅有一种方案位于(1,1),即原地不动。故模拟上述状态转移过程,即可得到坐标轴中各点的路径数量如下:

0	0	0	0	0	1	0	0	15	0	0	35
0	0	0	0	0	0	5	0	0	20	0	0
0	0	0	0	1	0	0	10	0	0	15	0
0	0	0	0	0	4	0	0	10	0	0	6
0	0	0	1	0	0	6	0	0	5	0	0
0	0	0	0	3	0	0	4	0	0	1	0
0	0	1	0	0	3	0	0	1	0	0	0
0	0	0	2	0	0	1	0	0	0	0	0
0	1	0	0	1	0	0	0	0	0	0	0
0	0	1	0	0	0	0	0	0	0	0	0
1	0	0	0	0	0	0	0	0	0	0	0

故正确答案为35

抉择吧 在苦与痛的深渊里

出题人:勋

题目大意

一共有100个位置,每个位置有一个数字x:
“c35bf5448f662d2cb3d77a34887c9637c4a835ba97344f729e3f8dd5975e266d42b35ec16ab67749a9a5762cceaace3e9dac”

在每个位置可以有三种选择到别的位置,问从位置1到位置100,最少需要经过多少个位置。

解题

经典的BFS问题,只需要从位置1开始每次往队列里面存入三个选择对应的位置并记录当前经过的位置个数。

当队头位置是100的时候就可以直接输出位置个数即可得到答案是12
(因此是采用的队列,所以第一次访问到位置100的时候必定是经过位置个数最少的时候—)

ps:因此数据少,很快就能得到答案,本题不需要做标记。否则需要做个vis标记,访问的不要再访问了,不然会重复走很多次,容易T。

代码

#include
using namespace std;
int x[105];
int vis[105];
int nxt[3]={1,-1,0};
int bias[3]={0,0,1};
int main()
{
	string s;
    cin>>s;
    for(int i=1;i<=s.size();i++)
    {
        x[i]=((s[i-1]>='a'&&s[i-1]<='z')?s[i-1]-'a'+10:s[i-1]-'0');
    }
    queue<pair<int,int> > q;
    q.push({1,1});
    while(q.size())
    {
        auto p=q.front();
        q.pop();
        if(p.first==100)
        {
            cout<<p.second;
            return 0;
        }
        for(int i=0;i<3;i++)
        {
            int pos = min(100,max(1,p.first+x[p.first]*nxt[i]+bias[i]));
            q.push({pos,p.second+1});
        }
    }
} 

美化字符串


出题人:举

题目大意

给定一个字符串 s s s,仅有大小写英文字母组成。何大佬觉得一些字符串非常丑,所以他想对字符串进行一些美化,使它边漂亮。漂亮的字符串应该满足以下三个条件:

  • $0 \le i,i+1 \le s.length-2 $
  • 如果 s [ i ] s[i] s[i]为小写字母,则 s [ i + 1 ] s[i+1] s[i+1]不可以是对应的大写字母。
  • 如果 s [ i ] s[i] s[i]为大写字母,则 s [ i + 1 ] s[i+1] s[i+1]不可以是对应的小写字母。

请你帮何大佬美化一下字符串,每次你可以从字符串中不满足上述条件的两个相邻字符进行删除,直到字符串满足上述条件。

空字符串也属于满足上述条件。

解题思路

从左到右扫描字符串 s 的每个字符。扫描过程中,维护当前整理好的字符串,记为 ret。当扫描到字符 ch 时,有两种情况:

  • 字符 ch 与字符串 ret 的最后一个字符互为同一个字母的大小写:根据题意,两个字符都要在整理过程中被删除,因此要弹出 ret 的最后一个字符。
  • 否则:两个字符都需要被保留,因此要将字符 ch 附加在字符串 ret 的后面。

分糖果


出题人:举

题目大意

n n n堆糖果 c a n d i e s candies candies,可以将每堆糖果分成任意数量的子堆,分开后无法再合并。

k k k个小孩,需要将这些糖果分给小孩,使得每个小孩分到的糖果数量一样。并且每个小孩至多拿一堆糖果。

问每个小孩最多可以拿走多少糖果

解题思路

根据题意,我们需要搜寻的目标是孩子最多可以拿走多少个糖果;如果把所有的糖果数加在一起对 k 取整可得每个孩子最多可以分得多少糖果,因此此问题就可以抽象转化为一个二分问题。定义一个分得糖果数量的中间值 mid ,每次都要更新按此糖果数目(mid)分的话一共可以分得堆数目(heap)若小于 k 则更新右端点high = mid - 1;如果按此糖果数目(mid)分的话一共可以分得堆数目若大于 k 则更新左端点low = mid + 1;知道最后low == high 结束循环即可得到结果。

代码实现

#include 
using namespace std;
class Solution {
   public:
    int maximumCandies(vector<int>& candies, long long k) {
        // 判断每个小孩分到 i 个糖果时是否可以满足要求
        auto check = [&](int i) -> bool {
            long long res = 0;
            for (int c : candies) {
                res += c / i;
            }
            return res >= k;
        };

        // 二分查找
        int l = 1;
        int r = 1 + *max_element(candies.begin(), candies.end());
        while (l < r) {
            int mid = l + (r - l) / 2;
            if (check(mid)) {
                l = mid + 1;
            } else {
                r = mid;
            }
        }
        return l - 1;
    }
};
void slove() {
    Solution s;
    long long n, k;
    cin >> n >> k;
    vector<int> v;
    for (int i = 0; i < n; i++) {
        int num;
        cin >> num;
        v.push_back(num);
    }
    cout << s.maximumCandies(v, k) << endl;
}
int main() {
    ios::sync_with_stdio(false);
 	slove();
    return 0;
}

快递站

出题人:弛

题目大意

给出一棵树,任选一个点x,使得 ∑ i = 1 n \sum_{i=1}^n i=1ndis( i i i, x x x) × a i \times a_i ×ai最大,dis表示两个点间的距离,中间间隔的边的数量

解题

(以1为根的前提下)定义sum[i]表示i和i所有子孙节点的权值的和,all为所有节点的权值和。

换根dp。先求出以1为根的情况下的结果,然后用这个结果去转换其他节点。

求1为根的时候:dfs算出每个点的深度,结果就是每个点的深度乘上这个点的权值。

转换的时候:从1这个根节点转换1的所有子节点,然后依次往下转换子节点。假设u为父节点,v为子节点,yuan定义为设x为u的情况下算出来的结果。那么当x为v的时候,相比于当x为u的情况 v以及v的所有子孙节点的权值都需要减少一次(因为他们对于v而言比对于u距离更近,差了1步),然后其他节点(all-sum[u])都需要多算一次(这些节点距离v比距离u更远,差了1步),所以对于v的结果now就是

now = yuan-sum[v]+all - sum[x];

代码

#include 
using namespace std;
#define ll long long
const int N = 200000 + 10;
int dep[N],vis[N];;
ll sum[N], a[N];
vector<int> g[N];
ll ans = 0, all = 0;
int place;
void dfs(int x, int fa, int de)
{
	dep[x] = de;
	for (auto it : g[x])
	{
		if (it == fa)
			continue;
		dfs(it, x, de + 1);
		sum[x] += sum[it];
	}
}
void dfs2(int x, int fa, ll yuan)
{
	ll now = yuan;
	now += all - sum[x];
	now -= sum[x];
	if (now > ans)
	{
		ans = now;
		place = x;
	}
	else if (now == ans)
	{
		place = min(place, x);
	}
	for (auto it : g[x])
	{
		if (fa == it)
			continue;
		dfs2(it, x, now);
	}
}
int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		all += a[i];
		sum[i] = a[i];
	}
	for (int i = 1; i < n; i++)
	{
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
   	dfs(1, 0, 0);
	for (int i = 1; i <= n; i++)
	{
		ans += a[i] * dep[i];
	}
	place = 1;
	ll temp = ans;
	for (auto it : g[1])
	{
		dfs2(it, 1, temp);
	}
	cout << place << " " << ans << endl;
}

以上这种写法有可能会爆栈,在是链的情况下会出现递归多次的情况,主要用于理解过程。

#include 
using namespace std;
#define ll long long
const int N = 200000 + 10;
int dep[N], vis[N];
ll sum[N], a[N], dp[N], fa[N];
vector<int> g[N];
ll ans = 0, all = 0;
int place;
queue<int> tree;
void dfs()
{
	queue<int> q;
	stack<int> temp;
	vis[1] = 1;
	q.push(1);
	q.push(-1);
	int de = 0;
	while (q.size() != 1)
	{
		int x = q.front();
		q.pop();
		if (x == -1)
		{
			de++;
			q.push(-1);
			continue;
		}
		temp.push(x);
		tree.push(x);
		vis[x] = 1;
		dep[x] = de;
		for (auto it : g[x])
		{
			if (!vis[it]) {
				fa[it] = x;
				q.push(it);
			}
		}
	}
	while (temp.size())
	{
		int x = temp.top();
		temp.pop();
		for (auto it : g[x])
		{
			if (vis[it] == 2)
				sum[x] += sum[it];
		}
		vis[x] = 2;
	}
}
void solve(int x, int fa)
{
	ll now = dp[fa];
	now += all - sum[x];
	now -= sum[x];
	if (now > ans)
	{
		ans = now;
		place = x;
	}
	else if (now == ans)
	{
		place = min(place, x);
	}
	dp[x] = now;
}
int main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		all += a[i];
		sum[i] = a[i];
	}
	for (int i = 1; i < n; i++)
	{
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	dfs();
	for (int i = 1; i <= n; i++)
	{
		ans += a[i] * dep[i];
	}
	place = 1;
	dp[1] = ans;
	tree.pop();
	while (tree.size()) {
		int x = tree.front();
		tree.pop();
		solve(x, fa[x]);
	}
	cout << place << " " << ans << endl;
}

汤神的积木

出题人:弛

题目大意

给出n个数字,要求相邻的数字大小不相同,对于每个数字可以执行加一操作,可以执行任意次,对不同位置的数字执行操作需要不同的花费,问满足条件的花费最少是多少。

解题

线性dp,通过观察可以发现,对于每个位置的操作不会超过两次,最多两次一定可以保证和两边的数字不相同(鸽笼原理)。

设置dp为二维,第一维表示数字的序号,第二维三种情况0,1,2表示加0次,1次,2次。dp[i][j]可以从dp[i-1][0],dp[i-1][1],dp[i-1][2]三个转换过来,前提是第i和第i-1个数字在经过操作后不相同

if (h[i - 1] + j != h[i] + k)
{
	dp[i][k] = min(dp[i][k], dp[i - 1][j] + k * b[i]);
}

代码

#include 
#define ll long long
using namespace std;
ll INF = 2e18;
const int maxn = 3e5 + 10;
ll h[maxn], b[maxn];
ll dp[maxn][3];

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    int T;
    cin >> T;
    while (T--)
    {
        int n;
        cin>>n;
        for (int i = 1; i <= n; i++)
        {
            cin >> h[i] >> b[i];
        }
        dp[1][0] = 0;
        dp[1][1] = b[1];
        dp[1][2] = b[1] * 2;
        for (int i = 2; i <= n; i++)
        {
            dp[i][0] = dp[i][1] = dp[i][2] = INF;
            for (int j = 0; j <= 2; j++)
            {
                for (int k = 0; k <= 2; k++)
                {
                    if (h[i - 1] + j != h[i] + k)
                    {
                        dp[i][k] = min(dp[i][k], dp[i - 1][j] + k * b[i]);
                    }
                }
            }
        }
        cout << min(dp[n][0], min(dp[n][1], dp[n][2])) << endl;
    }
    return 0;
}

两极反转

出题人:贝

题目大意

有这样一个字符串, S ( i ) S(i) S(i)表示第 i i i天这个字符串的值,该字符串仅由大写字母 L L L R R R的字符串组成,每过一天这个字符串的长度都会增长,如 S ( 1 ) = L , S ( 2 ) = L L R , S ( 3 ) = L L R L L R R S(1)=L,S(2)=LLR,S(3)=LLRLLRR S(1)=L,S(2)=LLR,S(3)=LLRLLRR

对于 ∀ i ∈ N ∗ \forall i \in N^* iN i > 1 i>1 i>1,有 S ( i ) = S ( i − 1 ) + L + r e v e r s e ( c o n v e r t ( S ( i − 1 ) ) ) S(i)=S(i-1)+L+reverse(convert(S(i-1))) S(i)=S(i1)+L+reverse(convert(S(i1)))

其中 r e v e r s e ( s ) reverse(s) reverse(s)函数表示,将整个字符串逆序; c o n v e r t ( s ) convert(s) convert(s)函数表示,将字符串中的所有 L L L变为 R R R,所有 R R R变为 L L L

某一天这个字符串的长度足够长时,你需要回答 T T T个询问,需要你回答出字符串中 [ a , b ] [a,b] [a,b]这个区间里面大写字母 L L L的数量,其中约定字符串下标从 1 1 1开始。

解题思路

不难看出S(i)的长度为 2 i − 1 2^i-1 2i1

  • S(i)中的L数量 = S(i-1)中的L数量 + 1(中间的那个1) + reverse(convert(S(i -1)))中的L数量

    = S(i -1)中的L数量 + 1(中间的那个1) + convert(S(i -1))中的L数量

    = S(i -1)中的L数量 + 1(中间的那个1) + S(i -1)中的R数量

    = S(i -1)的长度 + 1

    = 2 i − 1 2^{i-1} 2i1

设f(pos)为截止到第pos个字符出现的L的数量,那么我们所需要求解的答案即为 f® - f(L- 1),接下来的问题就转化为如何求解f(pos)了

我们设 b = 2 i b=2^i b=2i是最接近pos且小于pos的2的整数次幂

  • 第一种情况,若 2 i + 1 − 1 = p o s 2^{i+1}-1=pos 2i+11=pos,即 p o s pos pos刚好是一个整的S(j),这个时候我们就可以直接计算结果,数量为 2 i 2^i 2i个L

  • 第二种情况,若 b + 1 = p o s b+1=pos b+1=pos,即刚好比某个整的S(j)多出一个L,此时答案为 2 i − 1 + 1 2^{i-1}+1 2i1+1

  • 第三种情况,若 b + 1 < p o s b+1b+1<pos,则我们可以直接算出第二种情况的数量

m i d = b + 1 mid=b+1 mid=b+1

除此之外我们还需要计算mid这个位置右边那一串的L数量

因为是reverse过来的,所以我们可以将[mid+1,pos]这一段,看成是以mid为对称轴对称过来的。我们可以反过来计算[2 * mid + 2 - pos, mid - 1]这一段中R的数量,二者是等价的。我们用这一段的长度len 减去 这一段L的数量不就是R的数量吗?

newL = 2 * mid + 2 - pos, newR = mid - 1。然后我们计算的结果就是f(newR) - f(newL - 1)的值

其中因为newR又是第一种情况,故结果直接等于 2 i − 1 2^{i-1} 2i1,f(newL)我们可以递归像以上三种情况求解,如此反复,在O(log n)的复杂度内即可求解出答案

最后的求解过程大致,设 L = 1919 , R = 114514 L=1919,R=114514 L=1919,R=114514

pos = 114514, b = 65535
pos = 16557, b = 16383
pos = 16210, b = 8191
pos = 173, b = 127
pos = 82, b = 63
pos = 45, b = 31
pos = 18, b = 15
pos = 13, b = 7
pos = 2, b = 1

[1,114514]中的数量为57262个

pos = 1918, b = 1023
pos = 129, b = 127
pos = 126, b = 63
pos = 1, b = 0

[1,1918]中的L数量为961个

相减得到最后的答案为56301

代码实现

/*
LR序列函数,
递归分治,求解答案
一次询问O(logn)级别的算法
*/
#include 
#include 
using namespace std;
typedef long long LL;
inline int mxBit(LL pos)
{
	int num = 1, tot = -1;
	while(pos)
		pos >>= 1, tot ++;
	return tot;
}
LL divide(LL pos)
{
    LL i = mxBit(pos), p;
    //要求最接近pos的(2^n-1)是多少
    //就只有两种情况,因为知道pos的最高位是i,只有两种情况,一种是i个1,一种是i-1个1
    if ((1LL << (i + 1)) - 1 == pos)
    {                    //第一种
        return 1LL << i; // 1<
    }
    else
    { //第二种
        LL m = (1LL << i) - 1;
        LL res = (1LL << (i - 1)) + 1; // f(i-1)+L+reverse(i-1),此处大于,所以必然存在L,后面就加个1
        if (pos > m + 1)
        {                           //看是否存在reverse(i-1)
            LL l = 2 * m + 2 - pos; //以m+1为对称抽反转,
                                    /*翻转后
                                    (l+pos)/2=m+1
                                    l+pos=2*m+2
                                    l=2*m+2-pos;
                                    */
                                    //			LL r=m;
            res += (m - l + 1) - (/*divide(r)*/ (res - 1) - divide(l - 1));
            //右端点区间的L个数可以直接由公式求出,然后分治左端点,最后用前缀和思想
            //翻转后的L数量===长度-原来的L数量
            //长度==右端-左端+1==r-l+1==m-l+1
        }
        return res;
    }
}

LL cal(LL left, LL right)
{
    if (left == 1) //此处也可直接在divide里面设置,边界条件divide(0)==0
        return divide(right);
    else
        return divide(right) - divide(left - 1);
}

int main()
{
    cin.tie(0);
    cin.sync_with_stdio(false);
    cout.tie(0);
    int n;
    cin >> n;
    LL left, right;
    for (int i = 1; i <= n; ++i)
    {
        cin >> left >> right;
        cerr << left << ' ' << right << '\n';
        cout << cal(left, right) << endl;
    }
    return 0;
}

互不攻击的车

出题人:蔡万杰

题目大意

给定一个由 n n n 条列构成的不规则区域,需要从中放入 k k k 个车。求一共有多少种不同的方案。

解题

可以发现, n n n 条列左右任意交换,其总方案数是不会变的。先进行从小到达的排序,以便于之后的 d p dp dp

很显然,每列中最多放入一个车,因此每列只有两个抉择:要么放入一个车,要么不放车。

f [ i ] [ j ] f[i][j] f[i][j] 为:前 i i i 列,放入了 j j j 个车的情况下,一共有多少种方案。

初始状态: f [ i ] [ 0 ] = 1 , i ∈ [ 0 , n ] f[i][0]=1,i\in[0,n] f[i][0]=1,i[0,n]

转移方程: f [ i ] [ j ] = ( f [ i − 1 ] [ j − 1 ] × ( h [ i ] − j + 1 ) ) % m o d + f [ i − 1 ] [ j ] ) % m o d f[i][j]=(f[i-1][j-1]\times (h[i]-j+1))\%mod+f[i-1][j])\%mod f[i][j]=(f[i1][j1]×(h[i]j+1))%mod+f[i1][j])%mod

前半部分表示:从第 i i i 列选择任意一个位置放车的方案数。由于前面 i − 1 i-1 i1 列已经放了 j − 1 j-1 j1 个车,因此,第 i i i 列可以放置的位置只有: h [ i ] − j + 1 h[i]-j+1 h[i]j+1 种。

后半部分则表示:第 i i i 列不放车的方案数。

因此答案为: f [ n ] [ k ] f[n][k] f[n][k]

代码

#include
using namespace std;
const int N=1e3+10;

int n,k,T;
long long mod=1e9+7,h[N],f[N][N];
int main() {
	cin>>T;
	while(T--) {
		cin>>n>>k;
		for(int i=1; i<=n; i++)cin>>h[i];
		sort(h+1,h+n+1);
		for(int i=0;i<=n;i++){
			for(int j=0;j<=n;j++)f[i][j]=0;
		}
		f[0][0]=1;
		for(int i=1; i<=n; i++) {
			f[i][0]=1;
			for(int j=1; j<=k; j++) {
				f[i][j]=(f[i-1][j-1]*(h[i]-j+1)%mod+f[i-1][j])%mod;
			}
		}
		cout<<f[n][k]<<endl;
	}
	return 0;
}

白嫖才最香

出题人:蔡万杰

题目大意

给定一个拓扑图。

每个点上都有一个红包,但是 n n n 个点被分配给了 k k k 个公司。属于同一个公司红包,你只能选一个。求获取的红包价值的和的最大值。

解题

看到数据范围,显然想到状态压缩。

一共只有 k k k 种赞助。因此每个点只有 1 < < k 1<1<<k 种方案。然而还是过大。

但是,对于只有一个商铺的公司,是不需要记录状态的,遇到红包直接领掉就好了。

因此,状态最多只有: n / 2 n/2 n/2 种。

状态设置: f [ u ] [ j ] f[u][j] f[u][j] :到点 u u u ,状态为 j j j 的情况下,白嫖的最大金额。

转移方程:从 u u u v v v 转移

if(j&(1<<a[v])) {//如果该公司红包拿过了
    //替换
    f[v][j]=max(f[v][j],f[u][j^(1<<a[v])]+b[v]);
    //不替换
    f[v][j]=max(f[v][j],f[u][j]);
} else {//如果该公司红包没有拿过
    //拿
    f[v][j|(1<<a[v])]=max(f[v][j|(1<<a[v])],f[u][j]+b[v]);
    //继承
    f[v][j]=max(f[v][j],f[u][j]);
}

代码

#include
using namespace std;
const int N=30+10,M=1e6+10;

int a[N],b[N],vis[N],n,m,k,T,f[N][M];
int Map[N][N];

void clear() {
	for(int i=1; i<=n; i++)vis[i]=0;
	for(int i=1; i<=n; i++) {
		for(int j=1; j<=n; j++) {
			Map[i][j]=0;
		}
	}
	for(int i=1; i<=n; i++) {
		for(int j=0; j<(1<<k); j++) {
			f[i][j]=-1e9;
		}
	}
}
int main() {
	cin>>T;
	while(T--) {
		cin>>n>>m>>k;
		clear();
		map<int,int>num;
		for(int i=1; i<=n; i++) {
			cin>>a[i]>>b[i];
			a[i]--;
			num[a[i]]++;
		}
		k=0;
		for(auto [key,val]:num){
			if(val==1)num[key]=-1;
			else num[key]=k++;
		}
		for(int i=1;i<=n;i++)a[i]=num[a[i]];

		for(int i=1; i<=m; i++) {
			int u,v;
			cin>>u>>v;
			if(u>v)swap(u,v);
			if(u==v)continue;
			Map[u][v]=1;
		}
		f[1][0]=0;
		if(a[1]==-1)f[1][0]=b[1];
		else f[1][1<<a[1]]=b[1];

		for(int u=1; u<=n; u++) {
			for(int j=0; j<(1<<k); j++) {
				for(int v=1; v<=n; v++) {
					if(Map[u][v]==0)continue;
					if(a[v]==-1){
						f[v][j]=max(f[v][j],f[u][j]+b[v]);
						continue;
					}
					if(j&(1<<a[v])) {
						f[v][j]=max(f[v][j],f[u][j^(1<<a[v])]+b[v]);
						f[v][j]=max(f[v][j],f[u][j]);
					} else {
						f[v][j|(1<<a[v])]=max(f[v][j|(1<<a[v])],f[u][j]+b[v]);
						f[v][j]=max(f[v][j],f[u][j]);
					}
				}
			}
		}
		int ans=0;
		for(int i=1; i<=n; i++) {
			int t=-1e9;
			for(int j=0; j<(1<<k); j++) {
				ans=max(ans,f[i][j]);
				t=max(t,f[i][j]);
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

Pot

出题人:蔡万杰

题目大意

n n n 个数,初始均为 1 。两种操作,要么选择一个区间并乘上一个整数 x x x ;要么选择一个区间,并求该区间内,单个数的某个因子数的数量最大值。

解题

x ∈ [ 2 , 10 ] x\in[2,10] x[2,10],显然质因子只有 2 , 3 , 5 , 7 2,3,5,7 2,3,5,7

只需要用四个线段树维护区间因子个数最大值即可。剩下的就变成了:区间加法以及查询区间最大值

当然,也可以维护一个结构体线段树。

代码

#include 
using namespace std;
const int N=2e5+10;

struct node {
	int num[4];
	int lazy[4];
} tr[N*4];
int val[4];

void count(int x) {
	while(x%2==0) {
		x=x/2;
		val[0]++;
	}
	while(x%3==0) {
		x=x/3;
		val[1]++;
	}
	while(x%5==0) {
		x=x/5;
		val[2]++;
	}
	while(x%7==0) {
		x=x/7;
		val[3]++;
	}
}
void clear() {
	for(int i=0; i<4; i++)val[i]=0;
}
void pushup(int now) {
	for(int i=0; i<4; i++) {
		tr[now].num[i]=max(tr[now*2].num[i],tr[now*2+1].num[i]);
	}
}
void pushdown(int now,int l,int r) {
	for(int i=0; i<4; i++) {
		if(tr[now].lazy[i]) {
			tr[now*2].num[i]+=tr[now].lazy[i];
			tr[now*2+1].num[i]+=tr[now].lazy[i];
			tr[now*2].lazy[i]+=tr[now].lazy[i];
			tr[now*2+1].lazy[i]+=tr[now].lazy[i];
			tr[now].lazy[i]=0;
		}
	}
}
void update(int now,int l,int r,int ql,int qr) {
//	cout<
	if(ql<=l&&r<=qr) {
		for(int i=0; i<4; i++) {
			tr[now].num[i]+=val[i];
			tr[now].lazy[i]+=val[i];
		}
		return;
	}
	pushdown(now,l,r);
	int mid=(l+r)/2;
	if(ql<=mid)update(now*2,l,mid,ql,qr);
	if(qr>mid)update(now*2+1,mid+1,r,ql,qr);
	pushup(now);
}
int query(int now,int l,int r,int ql,int qr) {
	if(ql<=l&&r<=qr) {
		int res=-1;
		for(int i=0; i<4; i++)res=max(res,tr[now].num[i]);
		return res;
	}
	pushdown(now,l,r);
	int mid=(l+r)/2,res=-1;
	if(ql<=mid)res=max(res,query(now*2,l,mid,ql,qr));
	if(qr>mid)res=max(res,query(now*2+1,mid+1,r,ql,qr));
	return res;
}
int main() {
	int n,m;
	cin>>n>>m;
	for(int i=1; i<=m; i++) {
		int op,l,r,x;
		cin>>op;
		if(op==1) {
			cin>>l>>r>>x;
			count(x);
			update(1,1,n,l,r);
			clear();
		} else {
			cin>>l>>r;
			cout<<query(1,1,n,l,r)<<endl;
		}
	}
	return 0;
}

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号