每日一唐

每日一唐

Edu 177 D

https://vjudge.net/problem/CodeForces-2086D#author=DeepSeek_zh

rating:1687

题意

把一些字母组合成一个序列,要求任意同样的字母的位置的绝对差是偶数,求满足条件的系列的个数

唐点

偷看了一下题目tag,发现有个暴力,想到组成序列的限制条件其实就是一个字母只要有一个在偶数位置,则其他的相同字母都要在偶数位置,这样的话每个字母只有两种选择,计算了一下 \(2^{26}\)等于 \(7*10^7\)左右,以为暴力能过,结果就挂了。

正解:DP+组合数学

我们只需要dp找出恰好能将 n/2 的空间也就是偶数位置填满的 c中数字的方案数 也就是 排列数

现在确定了 偶数位有什么元素 对应 奇数位的元素种类也确定了。

接下来只需要将排列数转化为组合数即可。

代码

#include <bits/stdc++.h>

typedef std::pair<long long, long long> pll;
typedef std::pair<int, int> pii;
using i64 = long long;
const int mod=998244353;
#define int long long
const int maxn = 5e5 + 4;

std::vector<int> fac(maxn), inv(maxn);

// 快速幂
int quickPow(int a, int b)
{
    int ans = 1;
    while (b)
    {
        if (b & 1)
            ans = (ans * a) % mod;
        b >>= 1;
        a = (a * a) % mod;
    }
    return ans;
}
void init()
{
    // 求阶乘
    fac[0] = 1;
    for (int i = 1; i < maxn; i++)
    {
        fac[i] = fac[i - 1] * i % mod;
    }
    // 求逆元
    inv[maxn - 1] = quickPow(fac[maxn - 1], mod - 2);
    for (int i = maxn - 2; i >= 0; i--)
    {
        inv[i] = inv[i + 1] * (i + 1) % mod;
    }
}
int C(int n, int m)
{
    if (m > n)
    {
        return 0;
    }
    if (m == 0)
        return 1;
    return fac[n] * inv[m] % mod * inv[n - m] % mod;
}

void solve(){
	int n=0;
	std::array<int,26> a;
	for(int i=0;i<26;i++){
		std::cin>>a[i];
		n+=a[i];
	}
	int even=n/2,odd=(n+1)/2;
	std::vector<int> dp(even+1);
    dp[0]=1;
    for(int i=0;i<26;i++){
        if(!a[i])continue;
        for(int j=n/2;j>=0;j--){
            if(j+a[i]<=n/2)dp[j+a[i]]+=dp[j],dp[j+a[i]]%=mod;
        }
    }
    int ans=((dp[even]*fac[even])%mod*fac[odd])%mod;
    for(int i=0;i<26;i++){
        if(!a[i])continue;
        ans=(ans*inv[a[i]])%mod;
    }
    std::cout<<ans<<'\n';
}

signed main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(2);
	init();
	int t = 1, i;
	std::cin >> t;
	for (i = 0; i < t; i++){
		solve();
	}
	return 0;
}

十四届蓝桥杯国赛第一道填空题

https://www.lanqiao.cn/problems/17104/learning/

题目大意

连续写下1到2023之间的所有整数,得到一个字符串,问这字符串中多少个子序列恰好等于2023?

哪唐了?

思路完全正确,写代码时竟然犯了逆天错误,i从0开始,但是下面就是i-1,直接越界了。

for(int i=0;i<s.size();i++){
        f1[i]=f1[i-1];
        f2[i]=f2[i-1];
        f3[i]=f3[i-1];
        f4[i]=f4[i-1];
    	...

正解

子序列问题直接想到DP了,况且这道题还是非常朴素的计数DP,所以比较简单

#include <bits/stdc++.h>

#define int long long
// 5484660609
const int N = 7000;
signed main(){
    std::cout.tie(nullptr);
    std::ios::sync_with_stdio(false);

    std::string s="";
    for(int i=1;i<=2023;i++){
        int tm=i;
        std::string t="";
        while(tm){
            t.push_back(tm%10+'0');
            tm/=10;
        }
        std::reverse(t.begin(),t.end());
        s+=t;
    }

    std::vector<int> f1(N),f2(N),f3(N),f4(N);
    //分别表示:前缀2的个数,前缀20的个数,前缀202的个数,前缀2023的个数
    if(s[0]=='2')f1[0]=1;
    for(int i=1;i<s.size();i++){
        f1[i]=f1[i-1];
        f2[i]=f2[i-1];
        f3[i]=f3[i-1];
        f4[i]=f4[i-1];

        if(s[i]=='2'){
            f1[i]+=1;
            f3[i]+=f2[i-1];
        }else if(s[i]=='0'){
            f2[i]+=f1[i-1];
        }else if(s[i]=='3'){
            f4[i]+=f3[i-1];
        }
    }
    std::cout<<f4[s.size()-1]<<'\n';

    return 0;
}

十四届蓝桥杯国赛第二道填空题

https://www.lanqiao.cn/problems/17105/learning/

题目大意

若一个正整数 x 可以被表示为 \(p^2×q^2\),其中 p、q 为质数且 p≠q,则 x 是一个双子数。请计算区间 [2333,‭23333333333333‬] 内有多少个双子数

哪里唐了?

先不说此题暴力进行\(O(n^2)\)的枚举也可以过(超级不理解)。很明显此题是双指针或者二分的问题,双指针会更好写,但是我竟然没想到如何双指针,简直逆天。还有就是做了没必要的化简来缩小变量范围,其他题化简那是因为范围太大,但是这道题不化简,不去消平方,范围也是 long long 以内,完全可以接受,瞎化简啥?

正解:筛法+双指针

只有两个变量,很明显可以枚举一个变量确定另一个变量的范围

#include <bits/stdc++.h>
#define int long long
const int N = 1e7+5;
const int A = 2333;
const int B = 23333333333333;
int vis[N],prime[N],tot;

void init(){
    tot=0;
    vis[1]=1;
    for(int i=2;i<N;i++){
        if(!vis[i])prime[++tot]=i;
        for(int j=1;j<=tot;j++){
            if(i*prime[j]>=N||prime[j]*prime[j]*2*2>B)break;
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)break;
        }
    }
}

signed main(){

    init();
    int ans=0;
    int i=1;
    int r=tot;
    while(i<tot){
        while(prime[i]*prime[i]*prime[r]*prime[r]>B){
            r--;
        }
        int l=i+1;
        while(prime[i]*prime[i]*prime[l]*prime[l]<A){
            l++;
        }
        if(l>r)break;
        ans+=(r-l+1);
        i++;
    }
    std::cout<<ans<<'\n';
    return 0;
}

第十四届蓝桥杯国赛

https://www.lanqiao.cn/problems/17153/learning/

题目大意

班里有偶数个同学,老师给每名同学分配了id,现在要通过若干次更改,让每个id有且只有两名同学拥有,问最少修改几次。

哪唐了?

比较明显的是统计出现次数大于2次的id个数a和出现次数只有1次的id个数b,根据这两个数的大小判断最小更改次数。先想了想b>a的情况,误以为a>b时也一样,没有做更加细致的推导,导致出错

正解

#include <iostream>
const int N = 1e5+5;
using namespace std;
int arr[N]; 
 
int main()
{
  int n;
  cin>>n;
  for(int i=0; i<n; ++i){
    int num;
    cin>>num;
    arr[num]++;
  }
 
  int num1=0, num2=0;
  
  for(int i : arr){
    if(i>2) num1 += i-2; // 求取数量相同的数在减2;
    else if(i==1) num2++;
  }
 
  int sum = 0;
  // 当已被占用的id的数量,大于未被占用id时,那么sum = num1;
  if(num1>num2){
    sum = num1;  
  }else{ // num2<num1的情况
    sum = num2 + (num1-num2)/2;
  }
  cout<<sum<<endl;
  return 0;
}

codeforces395D

https://codeforces.com/contest/1945/problem/D

2025-4-13

题目大意

给定两个长度为 n 的数列 \(a1,a2,…,an,b1,b2,…,bn\)。当前你所处的位置为 n+1,你可以进行以下操作任意多次:

  • 设当前位置为 i,可以任意选择一个位置 \(j(1≤j<i)\),并与其交换位置,代价为 \(aj+\sum_{k=j+1}^{i-1}b_k\)

你需要求出移动到前 m 个位置的最小代价。

哪里唐了

想到了解法,也写对了代码,无奈答案求最小值,并且数都是\(10^9\)范围,ans数组设置的还是不够小(int范围),导致错了。

正解

假设只能一步到前m个位置,则贡献都需要加上b数组的后缀和。考虑中转的意义发现,中转能让我们在某个位置选择贡献a数组中的值,所以问题的核心就是在哪个idx选择a数组中的值。

代码

#include <bits/stdc++.h>

using u64 = unsigned long long;
using i64 = long long;
const int N = 1e5+5;
namespace ranges = std::ranges;
#define int long long
void solve(){
	int n,m;std::cin>>n>>m;
	std::vector<int> a(n+1),b(n+1);
	for(int i=1;i<=n;i++)std::cin>>a[i];
	for(int i=1;i<=n;i++)std::cin>>b[i];	
	std::vector<i64> suf(n+2);
	for(int i=n;i>=1;i--){
		suf[i]=std::min(a[i],b[i]);
		suf[i]+=suf[i+1];
	}
	i64 ans=1e18;
	for(int i=1;i<=m;i++){
		// std::cout<<i<<' '<<a[i]<<' '<<suf[i+1]<<'\n';
		ans=std::min(ans,1ll*a[i]+suf[i+1]);
	}
	std::cout<<ans<<'\n';
}

signed main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(2);
	int t = 1, i;
	std::cin >> t;
	for (i = 0; i < t; i++){
		solve();
	}
	return 0;
}
posted @ 2025-04-11 10:01  califeee  阅读(3)  评论(0)    收藏  举报