蓝桥杯百校真题大联赛第3期(五)好难哇

本文探讨了使用深度优先搜索(DFS)解决组合优化问题,包括重复覆盖模型和精确覆盖问题。文章提到了十字链表(dancing links)作为解法,并介绍了如何将问题转化为寻找最小公共集合。优化策略包括迭代加深、找选择最少的列以及可行性剪枝。此外,还涉及了数位动态规划解决组合数问题。最后,文章展示了如何处理平面切分、字符串排序以及左孩子右兄弟树的相关算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A组

糖果:重复覆盖模型

重复覆盖问题 和 精确覆盖问题
解法:十字链表dancing links
转化成,最小公共集合问题
优化方式:

  1. 迭代加深
  2. 找选择最少的列
  3. 可行性剪枝(估计函数h=最少需要选多少行)
    1和3 的组合就是A*

非优化版本

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;
const int N = 110, M = 1 << 20;

int n, m, k;
vector<int> col[N];
int log2[M];

int lowbit(int x)  // 返回末尾的1
{
    return x & -x;
}

bool dfs(int depth, int state)
{
    if (!depth) return state == (1 << m) - 1;
    
    int t = -1;
    for (int i = (1 << m) - 1 - state; i; i -= lowbit(i))
    {
        int c = log2[lowbit(i)];
        if (t == -1 || col[t].size() > col[c].size())
            t = c;
    }
    
    for (auto row : col[t])
        if (dfs(depth - 1, state | row))
            return true;
    return false;
}

int main()
{
    cin >> n >> m >> k;
    
    for (int i = 0; i < m; i ++ ) log2[1 << i] = i;
    
    for (int i = 0; i < n; i ++ )
    {
        int state = 0;
        for (int j = 0; j < k; j ++ )
        {
            int c;
            scanf("%d", &c);
            state |= 1 << c - 1;
        }
        
        for (int j = 0; j < m; j ++ )
            if (state >> j & 1)
                col[j].push_back(state);
    }
    
    int depth = 0;
    while (depth <= m && !dfs(depth, 0)) depth ++ ; // 没有全选并且还不能凑齐
    
    if (depth > m) depth = -1;
    cout << depth << endl;
    
    return 0;
}

优化版本

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;
const int N = 110, M = 1 << 20;

int n, m, k;
vector<int> col[N];
int log2[M];

int lowbit(int x)  // 返回末尾的1
{
    return x & -x;
}

int h(int state) // 至少需要再选几行
{
    int res = 0;
    for (int i = (1 << m) - 1 - state; i; i -= lowbit(i))
    {
        int c = log2[lowbit(i)];
        res ++ ;
        for (auto row : col[c]) i &= ~row;
    }
    return res;
}

bool dfs(int depth, int state)
{
    if (!depth || h(state) > depth) return state == (1 << m) - 1;
    
    int t = -1;
    for (int i = (1 << m) - 1 - state; i; i -= lowbit(i))
    {
        int c = log2[lowbit(i)];
        if (t == -1 || col[t].size() > col[c].size())
            t = c;
    }
    
    for (auto row : col[t])
        if (dfs(depth - 1, state | row))
            return true;
    return false;
}

int main()
{
    cin >> n >> m >> k;
    
    for (int i = 0; i < m; i ++ ) log2[1 << i] = i;
    
    for (int i = 0; i < n; i ++ )
    {
        int state = 0;
        for (int j = 0; j < k; j ++ )
        {
            int c;
            scanf("%d", &c);
            state |= 1 << c - 1;
        }
        
        for (int j = 0; j < m; j ++ )
            if (state >> j & 1)
                col[j].push_back(state);
    }
    
    int depth = 0;
    while (depth <= m && !dfs(depth, 0)) depth ++ ; // 没有全选并且还不能凑齐
    
    if (depth > m) depth = -1;
    cout << depth << endl;
    
    return 0;
}

组合数问题:数位DP(表示不会)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const ll MOD = 1E9 +7;

ll t, k;
ll n, m, ans;
ll a[70], b[70];
ll dp[70][4];
int lena, lenb;

ll ans0(ll x, ll y) {
    if (x < 0 || y < 0) return 0;
    if (x <= y) return (x % MOD + 2) * (x % MOD + 1) / 2 % MOD;
    x %= MOD, y %= MOD;
    return ((y + 2) * (y + 1) / 2 % MOD + (x - y) * (y + 1) % MOD) % MOD;
}

ll ans1(ll x, ll y) {
//    if (x < 0 || y < 0) return 0;
    return min(x, y) + 1;
}

ll ans2(ll x, ll y) {
//    if (x < 0 || y < 0) return 0;
    return max(x - y + 1, 0LL);
}

int main() {
    cin >> t >> k;
    while (t--) {
        cin >> n >> m;
        if (m > n) m = n;
        ans = ans0(n, m);
        memset(b, 0, sizeof b);
        for (lena = 0; n; lena++, n /= k) a[lena] = n % k;
        for (lenb = 0; m; lenb++, m /= k) b[lenb] = m % k;
        n = lena;
//        memset(dp, 0, sizeof dp);
        dp[n][0] = dp[n][1] = dp[n][2] = 0;
        dp[n][3] = 1;
        for (int i = n - 1; i >= 0; i--) {
            dp[i][0] = dp[i + 1][0] * ans0(k - 1, k - 1);
            dp[i][0] += dp[i + 1][1] * ans0(a[i] - 1, k - 1) + dp[i + 1][2] * ans0(k - 1,b[i] - 1);
            dp[i][0] += dp[i + 1][3] * ans0(a[i] - 1, b[i] - 1);
            
            dp[i][1] = dp[i + 1][1] * ans1(a[i], k - 1) + dp[i + 1][3] * ans1(a[i], b[i] - 1);
            
            dp[i][2] = dp[i + 1][2] * ans2(k - 1, b[i]) + dp[i + 1][3] * ans2(a[i] - 1, b[i]);
            
            dp[i][3] = ((a[i] >= b[i]) && dp[i + 1][3]);
            dp[i][0] %= MOD; dp[i][1] %= MOD; dp[i][2] %= MOD;
        }
        ans -= dp[0][0] + dp[0][1] + dp[0][2] + dp[0][3];
        ans = (ans % MOD + MOD) % MOD; 
        cout << ans << endl;
    }
    return 0;
}

B组

平面切分

#include <iostream>
#include <cstring>
#include <algorithm>
#include <set>
#define x first
#define y second

using namespace std;
const int N = 1010;
typedef pair<long double, long double> PDD;
typedef long long LL;

LL ans;
PDD p;
long double s[N][2];
bool st[N];

int main()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; i ++ )
    {
        cin >> s[i][0] >> s[i][1];
        set<PDD> S;
        for (int j = 0; j < i; j ++ )
        {
            if (st[j]) continue;
            if (s[i][0] == s[j][0])
            {
                if (s[i][1] == s[j][1])
                {
                    st[i] = true;
                    break;
                }
                else continue;
            }
            
            p.x = (s[j][1] - s[i][1]) / (s[i][0] - s[j][0]);
            p.y = s[i][0] * p.x + s[i][1];
            S.insert(p);
        }
        if (!st[i]) ans += S.size() + 1;
    }
    cout << ans + 1 << endl;
    return 0;
}

字串排序

import java.util.Scanner;
public class Main {
	static int list[]={//存放后缀序列,这样插和删除很容易
		0,0,0,0,0,//注cccbba=1,2,3,0,……
		0,0,0,0,0,
		0,0,0,0,0,
		0,0,0,0,0,
		0,0,0,0,0,
		0
	};
	static int[] str=new int[300];//存放前缀序列
	static void reset() {//后缀序列清零
		int i=0;
		while(i<26&&list[i]!=0) {
			list[i]=0;
			++i;
		}
	}
	static int getrnum() {//计算逆序数(分三步)
		int cnt=0;
		for(int i=0;str[i]!=0;++i) {//前缀的逆序数
			for(int j=i;str[j]!=0;++j) {
				if(str[i]>str[j]) {
					++cnt;
				}
			}
		}
		for(int i=0;str[i]!=0;++i) {//前缀对后缀的逆序数
			for(int j=25;j>=0;--j) {
				if(str[i]-'a'>j) {
					cnt+=list[j];
				}
			}
		}
		int temp=0;
		for(int i=0;i<26;++i) {//后缀的逆序数
			cnt+=temp*list[i];
			temp+=list[i];
		}
		return cnt;
	}
	static int getinc(int c) {//获得最大逆序增量(特殊步骤中代替求逆序数函数用来提速)(可以认为在数字符串里有多少非c(传入的参数)字符)(也就是插入c逆序数能增加多少)
		int i=0,cnt=0;
		while(str[i]!=0) {
			if(str[i]>(c+'a')) {
				cnt++;
			}
			++i;
		}
		for(i=0;i<26;++i) {
			if(i!=c) {
				cnt+=list[i];
			}
		}
		return cnt;
	}
	static void set() {//在后部序列中插入元素,保证逆序数最大
		int max=0,temp=0,index=0;
		for(int i=0;i<26;++i) {
			list[i]++;
			if((temp=getinc(i))>max) {//找出使逆序数增得最快的字符插入(这里比用增而直接记录逆序数不影响结果,但慢一些,数据10000左右要5秒左右,会超时的,不然我也不会编这么个对于的函数。。)
				index=i;
				max=temp;
			}
			list[i]--;
		}
		list[index]++;
	}
	static void getMaxStr(int l) {//获取前缀确定且长度确定的前提下的最大逆序数字串
		reset();
		for(int i=0;str[i]!=0;++i,--l);
		while(l>0) {
			set();
			--l;
		}
	}
	static void printstr() {//打印目标字符串
		String Str="";
		int i=0;
		while(str[i]!=0) {
			Str+=(char)str[i];
			++i;
		}
		for(i=25;i>=0;--i) {//这里其实没用,既然不执行也不会影响效率,留着吧,后缀最后是空的,但曾经存在过。。。
			for(int j=0;j<list[i];++j) {
				Str+=(char)(i+'a');
			}
		}
		System.out.println(Str);
	}
	static void getans(int num,int l) {//l是字串长度
		for(int i=0;i<l;++i) {
			for(int j=0;j<26;++j) {//每个位从a开始试
				str[i]=j+'a';
				getMaxStr(l);//获取指定前缀最大逆字串
				if(getrnum()>=num) {//超了就下一个
					break;
				}
			}
		}
	}
	public static void main(String[] args){//这了很简洁了
		int num;
		Scanner sc = new Scanner(System.in);
		num=sc.nextInt();//获取输入
		sc.close();
		int l=0;
		while(getrnum()<num) {//获取最短字串长
			++l;
			getMaxStr(l);
		}
		getans(num,l);//获得目标字串
		printstr();//打印
	}
}

C组

左孩子右兄弟

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;
const int N = 100010;
vector<int> g[N];
int n;

int dfs(int u)
{
    int ans = 0;
    int cnt = g[u].size();
    for (int i = 0; i < cnt; i ++ )
    {
        ans = max(ans, dfs(g[u][i]));
    }
    return ans + cnt;
}

int main()
{
    cin >> n;
    for (int i = 2; i <= n; i ++ )
    {
        int x;
        scanf("%d", &x);
        g[x].push_back(i);
    }
    
    int ans = dfs(1);
    cout << ans << endl;
    return 0;
}

括号序列

合法括号序列的性质:

  1. 左右括号相同
  2. 任何一个前缀中,左括号数>=右括号数量
#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;
const int N = 5010, MOD = 1e9 + 7;
typedef long long LL;

int n;
char s[N];
LL f[N][N];

LL work()
{
    memset(f, 0, sizeof f);
    f[0][0] = 1; // 不选择字符时,左括号比右括号多0, 
    // 这个时候不添加左括号也是一种方案方案数为1
    
    for (int i = 1; i <= n; i ++ )
    {
        if (s[i] == '(')
        {
            for (int j = 1; j <= n; j ++ )
                f[i][j] = f[i - 1][j - 1];
        }
        else
        {
            f[i][0] = (f[i - 1][1] + f[i - 1][0]) % MOD;
            for (int j = 1; j <= n; j ++ )
            {
                f[i][j] = (f[i - 1][j + 1] + f[i][j - 1]) % MOD;
            }
        }
    }
    
    for (int i = 0; i <= n; i ++ )
        if (f[n][i])
            return f[n][i];
    return -1;
}

int main()
{
    scanf("%s", s + 1);
    n = strlen(s + 1);
    
    LL l = work();
    reverse(s + 1, s + n + 1);
    for (int i = 1; i <= n; i ++ )
    {
        if (s[i] == '(') s[i] = ')';
        else s[i] = '(';
    }
    
    LL r = work();
    printf("%lld", (l * r) % MOD);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Shirandexiaowo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值