每日一唐
每日一唐
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;
}
十四届蓝桥杯国赛第一道填空题
题目大意
连续写下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;
}
十四届蓝桥杯国赛第二道填空题
题目大意
若一个正整数 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;
}
第十四届蓝桥杯国赛
题目大意
班里有偶数个同学,老师给每名同学分配了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;
}