A - A
算法分析
要想让 只能是发生在进位的时候,因此每逢尾数为 9 时就会对答案产生贡献。
AC code
#include<bits/stdc++.h>
using namespace std;
void solve()
{
int n,res = 0;
cin >> n;
if(n % 10 == 9) res += 1;
res += n / 10;
cout << res << endl;
}
int main()
{
int T;
cin >> T;
while(T --)
solve();
return 0;
}
B - B
算法分析
一看这题面就有背包问题的影子。但是直接背包的话复杂度必定爆炸,因此我们考虑怎么优化优化。
本题需要注意到的第一个点就是问的是子序列而不是子串,因为数之间是可以隔开的。
然后题目要求的是从 的序列中找元素之和能够被
整除的非空子序列
我们可以注意到一个地方,若 ,那么对于这个序列的前缀和而言,根据抽屉原理,由于
对
取模的结果必定是在
的范围内,那么最坏的情况也已经保证了
区间内的数至少各有一个,那么必定存在
使得
那么
至
的这一段必定会被
整除,因此满足了题意。(不在最坏情况的话,那么数已经出现重复,必然会满足题意)
那么我们再看看 的情况, ,那么最坏的情况也已经保证了
一定会出现,那么已经达成条件。
那么最后再看看 的情况,直接背包就完事了
状态表示
表示从前
个数中选一个子集,使得其和
以后为
的方案数,那么最后本题目的答案就是
是否大于
,(因为存在空集的可能性)
状态转移
就是考虑是否选择第 个数
第 个数若是不选的话,那么可以直接从
转移过来
第 个数若是选的话,那么在第
的时候的取模值为
才行,考虑到取模的溢出记得加上
。
问题就解决了,上代码。
注意一个细节,转移的过程中会因为方案数过多,炸范围,因为当
时,我们都将其赋值为
即可
AC code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
void solve()
{
int n,m;
cin >> n >> m;
LL a[n + 1],f[m + 1][m + 1];
for(int i = 1;i <= n;i ++) scanf("%lld",&a[i]),a[i] %= m;
if(n >= m) puts("YES");
else
{
memset(f,0,sizeof f);
f[0][0] = 1;
for(int i = 1;i <= n;i ++)
for(int j = 0;j <= m - 1;j ++)
{
f[i][j] = f[i - 1][j] + f[i - 1][(j - a[i] + m) % m];
if(f[i][j] >= 2) f[i][j] = 2;
}
if(f[n][0] == 2) puts("YES");
else puts("NO");
}
}
int main()
{
int T = 1;
//cin >> T;
while(T --)
solve();
return 0;
}
C - C
算法分析
简单思维题 没啥好写的
AC code
#include<bits/stdc++.h>
using namespace std;
void solve()
{
int n,sum = 0;
cin >> n;
int a[n + 1];
for(int i = 1;i <= n;i ++) scanf("%d",&a[i]),sum += a[i];
if(sum < n) puts("1");
else if(sum == n) puts("0");
else cout << sum - n << endl;
}
int main()
{
int T;
cin >> T;
while(T --)
solve();
return 0;
}
D - E
算法分析
组合数学
进行 次移动后,至多会产生
个空房间,那么设当前产生了
个空房间,那么也就是说现在要将
个人,分配到
个房间中去,利用隔板法。
个人之间有
个可插隔板的位置,要分成
份则需要
块挡板,那么就是
,再去考虑从
个房间中选出
个空房间为
答案就是对 求和就完事了
AC code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
LL fac[N],inv[N];
LL qmi(LL a, LL k, LL p) // 求a^k mod p
{
LL res = 1 % p;
while (k)
{
if (k & 1) res = res * a % p;
a = a * a % p;
k >>= 1;
}
return res;
}
void fc(int n)
{
inv[0] = fac[0] = 1;
for(int i = 1;i <= n;i ++) fac[i] = (i * fac[i - 1]) % mod,inv[i] = qmi(fac[i],mod - 2,mod);
}
LL c(LL a,LL b)
{
return fac[a] * inv[b] % mod * inv[a - b] % mod;
}
void solve()
{
LL n,k;
cin >> n >> k;
k = min(k,n - 1);
fc(N);
LL res = 0;
for(int i = 0;i <= k;i ++)
res = (res + c(n,i) * c(n - 1,i) % mod) % mod;
cout << res << endl;
}
int main()
{
int T = 1;
//cin >> T;
while(T --)
solve();
return 0;
}
E - F
算法分析
题目求的是加边的方案数,使得其最短距离不改变。
我们来一起回忆一下,学最短路的时候是怎么思考的
通过不断松弛,使得其最短。
那么这题可以借用这样子思想去思考
我们已经确定了起点和终点,那么我们只需要枚举 起点到 的距离 + 终点到
的距离 +
之间的距离判断其是否大于等于最短距离,如果满足那么这个
组合就是合法的加边结果。
那么这样子思路就很简单了,我们对起点和终点分别跑两次最短路,枚举要加边的两个端点,判断其是否满足条件,计算其对答案的贡献即可。
AC code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int vis[N][N];
vector<int> g[N];
int dist_s[N],dist_t[N];
typedef long long LL;
void solve()
{
int n,m,s,t;
cin >> n >> m >> s >> t;
while(m --)
{
int a,b;
scanf("%d%d",&a,&b);
g[a].push_back(b);
g[b].push_back(a);
vis[a][b] = vis[b][a] = 1;
}
memset(dist_s,- 1,sizeof dist_s);
memset(dist_t,- 1,sizeof dist_t);
queue<int> q;
q.push(s);
dist_s[s] = 0;
while(q.size())
{
auto u = q.front();
q.pop();
for(int i = 0;i < g[u].size();i ++)
{
int v = g[u][i];
if(dist_s[v] == -1) dist_s[v] = dist_s[u] + 1,q.push(v);
}
}
q.push(t);
dist_t[t] = 0;
while(q.size())
{
auto u = q.front();
q.pop();
for(int i = 0;i < g[u].size();i ++)
{
int v = g[u][i];
if(dist_t[v] == -1) dist_t[v] = dist_t[u] + 1,q.push(v);
}
}
LL res = 0;
for(int i = 1;i <= n;i ++)
for(int j = i + 1;j <= n;j ++)
if(!vis[i][j] && dist_s[i] + 1 + dist_t[j] >= dist_s[t] && dist_t[i] + 1 + dist_s[j] >= dist_s[t])
res ++;
cout << res << endl;
}
int main()
{
int T = 1;
while(T --) solve();
return 0;
}
F - G
算法分析
又双叒叕是我们亲爱的线段树喽,可以说是每场必出的考点了
就是拿线段树去维护前 个的最小花费即可
AC code
#include <bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;
vector <pair <int,int> >in[maxn],out[maxn];
struct NODE
{
int l,r;
long long sum,num;
} p[maxn*4];
int n,m,k;
void build(int l,int r,int cur)
{
p[cur].l=l;
p[cur].r=r;
p[cur].num=p[cur].sum=0;
if(l==r)
return ;
int mid=(l+r)/2;
build(l,mid,cur*2);
build(mid+1,r,cur*2+1);
}
void pushup(int k)
{
p[k].num=p[k*2].num+p[k*2+1].num;
p[k].sum=p[k*2].sum+p[k*2+1].sum;
}
void update(int num,int pri,int cur)
{
int l=p[cur].l,r=p[cur].r;
if(l==r)
{
p[cur].num+=num;
p[cur].sum+=(long long)num*pri;
return ;
}
int mid=(l+r)/2;
if(pri<=mid)
update(num,pri,2*cur);
else
update(num,pri,2*cur+1);
pushup(cur);
}
long long query(int num,int cur)
{
int l=p[cur].l,r=p[cur].r;
if(l==r)
return (long long)l*min(p[cur].num,(long long)num);
if(num<=p[cur*2].num)
return query(num,cur*2);
else
return p[cur*2].sum+query(num-p[cur*2].num,cur*2+1);
}
int main()
{
int l,r,x,y;
scanf("%d%d%d",&n,&k,&m);
for(int i=0; i<=n; i++)
{
in[i].clear();
out[i].clear();
}
for(int i=1; i<=m; i++)
{
scanf("%d%d%d%d",&l,&r,&x,&y);
in[l].push_back(make_pair(x,y));
out[r].push_back(make_pair(x,y));
}
long long ans=0;
build(1,maxn,1);
for(int i=1; i<=n; i++)
{
for(int j=0; j<in[i].size(); j++)
update(in[i][j].first,in[i][j].second,1);
ans+=query(k,1);
for(int j=0; j<out[i].size(); j++)
update(-out[i][j].first,out[i][j].second,1);
}
printf("%lld\n",ans);
return 0;
}
G - I
算法分析
构造题,首先考虑不可能的情况
样例中就给了我们很好的提示, 时必定不成立(srds 这个也是显而易见的)
然后还有一个就是 时,节点数只能是
然后再考虑怎么建树
我的第一想法是先构建成一条满足条件的链,然后不够的再往上加点即可
比如说一颗高度为 ,最大长度为
的一颗树,我们只需要在
号结点一侧连接
个点,另外一侧连接
个点,这里就会构造成一个节点数为
的链,然后剩余的结点为了防止超过最大长度直接加到
结点上就行了,因为倘若会有剩余的结点说明构造完
个点后另外一侧至少存在一个点,那么加到
结点上必然是不会超的。
然后有个特殊的情况就是 的情况,这是我
的时候才发现的,构造
的时候结果不对,于是考虑到了
的情况,
意味着构造完一侧后另外一侧不允许再加边了,但是此时
还有剩余的话怎么办呢,给他全加到
节点上就搞定了。
AC code
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
void solve()
{
int n,d,h;
cin >> n >> d >> h;
if(d > 2 * h || (d < 2 && n != 2))
{
puts("-1");
return ;
}
if (h == d)
{
for (int i = 1;i <= h;i ++)
{
printf("%d %d\n",i,i + 1);
}
for (int i = h + 2;i <= n;i ++)
{
printf("%d %d\n",2,i);
}
return ;
}
else
{
for(int i = 1;i <= h;i ++)
printf("%d %d\n",i,i + 1);
if(d - h >= 1)
{
printf("%d %d\n",1,h + 2);
for(int i = 1;i <= d - h - 1;i ++)
{
printf("%d %d\n",h + 1 + i,h + 2 + i);
}
if(n - d - 1 >= 1)
{
int left = n - d - 1;
for(int i = d + 2;left > 0;i ++)
{
printf("%d %d\n",1,i);
left --;
}
}
}
}
}
int main()
{
int T = 1;
//cin >> T;
while(T --)
solve();
return 0;
}
H - J
算法分析
将一个长度为偶数的区间分割为两部分,且两部分元素异或和相同
看到两部分元素异或和相同,那么可以想到这两部分异或后的结果是
那么本题见变成了 对于一段区间 来说要使得他的异或值为
, 那么就是
那么就是统计有多少个区间使得 即可
然后最后注意一下,区间长度是偶数这个限制条件即可
AC code
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e6 + 10;
typedef long long LL;
LL a[N],b[N];
LL vis_odd[N],vis_even[N];
void solve()
{
LL n,tmp = 0;
cin >> n;
LL res = 0;
vis_even[0] ++;//初始状态的前缀为 0,且为偶数(区间为空)
for(int i = 1;i <= n;i ++)
{
int x;
cin >> x;
tmp = tmp ^ x;
if(i % 2 == 1)
{
res += vis_odd[tmp];
vis_odd[tmp] ++;
}
else
{
res += vis_even[tmp];
vis_even[tmp] ++;
}
}
cout << res << endl;
}
int main()
{
int T = 1;
//cin >> T;
while(T --) solve();
return 0;
}
I - K
算法分析
线性dp
为啥想到这题是dp呢,因为这题长得就很dp(bushi)
很明显的就是让你求解整个序列上的极值问题,然后判断其是否无后效性和是否存在最优子结构就能够知道是不是个 dp ,这题整个序列上的极值显然是可以由其子序列转移而来的,具体证明就不多赘述,直接开始dp
状态表示
表示前
个数字中取
(向下取整)个数并且这些数两两不相邻,其最大的和
状态转移
由于是 (向下取整)存在奇偶数不同的情况,因此我们对其分类讨论
若是奇数并且选了第 个数则
,因为选了第
个数后,第
个数就不能被选中了,只能从
转移而来
若没有选第 个数则
若是偶数并且选了第 个数则同理,
,
若没有选第 个数,我们只能将奇数位置上的数全选上才能有
(向下取整)个数字,不然的话若还是
,此时
为奇数除二下取整会比原来少一个就不符合题目的条件了。
不妨我们举个例子来看看
ex.1 2 3 4
那么我们总共需要取
个数
若我们取了 ,那么根据转移方程我们要从
转移那么就是
,
中可以取到
个数,共计两个满足条件
那么若我们不取 那么我们需要从
中取出两个数来,但是这是不符合我们状态方程的定义的,而要取出两个数的唯一办法就是取奇数位上的数字
才能满足条件
因此转移方程表示成了 ,
表示为奇数位次上的前缀和
for(int i = 2;i <= n;i ++)
{
if(i % 2 == 1)
{
f[i] = max(f[i - 2] + a[i],f[i - 1]);
}
else
{
f[i] = max(f[i - 2] + a[i],s[i - 1]);
}
}
AC code
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
LL a[N],f[N],s[N];
void solve()
{
int n;
cin >> n;
for(int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
s[1] = a[1];
for(int i = 3;i <= n;i += 2) s[i] = s[i - 2] + a[i];
for(int i = 2;i <= n;i ++)
{
if(i % 2 == 1)
{
f[i] = max(f[i - 2] + a[i],f[i - 1]);
}
else
{
f[i] = max(f[i - 2] + a[i],s[i - 1]);
}
}
cout << f[n] << endl;
}
int main()
{
int T = 1;
while(T --)
solve();
return 0;
}
写在最后
由于时间原因和作者本身能力问题,本题解难免会出现部分纰漏,还希望大家多多批评指正。
码字不易,点个赞再走吧