2021杭电多校赛2021“MINIEYE杯”中国大学生算法设计超级联赛签到题4题
Posted 小哈里
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2021杭电多校赛2021“MINIEYE杯”中国大学生算法设计超级联赛签到题4题相关的知识,希望对你有一定的参考价值。
Solved Pro.ID Title Ratio(Accepted / Submitted)
1001 Miserable Faith 33.33%(19/57)
1002 String Mod 26.50%(31/117)
1003 VC Is All You Need 41.56%(845/2033)(结论)
1004 Another String 57.30%(102/178)
1005 Random Walk 2 52.67%(79/150)
1006 Cute Tree 64.94%(876/1349)(模拟)
1007 Banzhuan 19.98%(664/3324)(贪心)
1008 Supermarket 61.40%(35/57)
1009 Array 15.03%(112/745)思维
1010 Guess Or Not 2 0.00%(0/7)
1011 Jsljgame 1.22%(1/82)
1012 Yet Another Matrix Problem 15.45%(17/110)
1013 Penguin Love Tour 14.83%(39/263)
1003 VC Is All You Need
题意:
- n维空间中,求m的最大值,使得你可以找到m个点(自己给定坐标),满足:无论对这m个点如何二染色,也就是对于2^m种染色方案中的每一种,都总存在一个n-1维超平面,严格分开这两种颜色的点。
思路:
- 结论:Max{m} = n+1
- 证明:显然有单调性,可以分两步证明结论,
1,证明m=n+1可行。
2,证明m>=n+2无解。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int T; cin>>T;
while(T--){
LL n, k; cin>>n>>k;
if(k>=n-1)cout<<"Yes\\n";
else cout<<"No\\n";
}
return 0;
}
1006 Cute Tree
题意:
- 给出一个假算法,用一个1-n的序列建立一棵三叉树,求树上的节点个数
思路:
- 模拟:加一个递归边界,去掉统计类的无用的a, key, son数组,直接输出节点个数tot就过了
#include<bits/stdc++.h>
using namespace std;
int tot;
void build(int l, int r){
if(l>r)return ;//RE
tot = tot+1;
if(l==r)return ;
if(r-l+1==1){
int mid=(l+r)/2;
build(l,l);
build(r,r);
}else{
int b = l+(r-l+1)/3-1;
int c = (b+r)/2;
build(l,b);
build(b+1,c);
build(c+1,r);
}
}
int main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T; cin>>T;
while(T--){
tot = 0;
int n; cin>>n;
for(int i = 1; i <= n; i++){int x; cin>>x;}
build(1, n);
cout<<tot<<"\\n";
}
return 0;
}
1007 Banzhuan
题意:
- 给出一个n,用1*1*1的方块去摆n*n*n的图形,满足从前,左,右,三个方向看去,都是n*n的矩形,每个方块的代价为x*y*y*z,求最小代价。
思路:
- 贪心:底面铺一层,在左边和前面的边界竖着z轴竖起来两面墙。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL mod = 1e9+7;
//快速幂求逆元
LL pows(LL a, LL x, LL p){if(x==0)return 1; LL t = pows(a, x>>1,p);if(x%2==0)return t*t%p;return t*t%p*a%p;}
LL inv(LL x, LL p){ return pows(x,p-2,p);}
int main(){
int T; cin>>T;
while(T--){
LL n; cin>>n; n%=mod;
LL a=n*(n+1)%mod*(2*n+1)%mod*inv(6,mod)%mod;//1^2+2^2+...+n^2
LL b=(2LL+n)%mod*(n-1)%mod*inv(2,mod)%mod;//2+3+...+n
LL c=((1LL+n)%mod*n%mod*inv(2,mod)%mod+mod)%mod;//1+2+3+...+n
LL x=(((((a+b)%mod-1)%mod+mod)%mod*b%mod+a*c%mod)%mod+mod)%mod; //x=(a+b-1)*b+a*c;
LL y=((a*n%mod*c%mod)*n%mod+mod)%mod;//y=(a*n*c)*n;
cout<<x<<"\\n"<<y<<"\\n";
}
return 0;
}
1009 Array
题意:
- 给出一个长为n的序列,求所有的n*(n+1)/2个子区间中,有多少个区间满足区间内存在某个数大于区间长度的一半。
思路:
Part1
- 显然对于合法的区间,众数是唯一的,因此不妨枚举众数,将众数标记为1、其余数标记为-1,此时问题即求有多少个区间和大于0
- 考虑暴力的做法:从左到右枚举右端点,记当前前缀和为sum,即查询之前有多少个前缀和小于sum
- 具体的,记f(i)为(当前)有多少个前缀和为i,每次的查询累加即为∑[i=min,sum−1]f(i),同时并将f(sum)加1.(其中min为历史前缀最小值,初始为f0=1且∀i≠0,fi=0)
- 由于sum的变化是连续的,不难线性维护∑[i=min,sum−1]f(i),时间复杂度为o(n^2)
————
Part2
- 进一步的,注意到1的段数和是o(n)的,-1的段数和也是o(n)的,因此将连续的一段-1一起处理(存的时候存每个众数出现的位置,中间都是-1)
- 具体的,考虑处理一段-1,假设这段-1之前的前缀和为sum,这段-1的长度为len,即段区间的查询结果为∑[i=sum−len, sum−1]∑[j=min,i−1]f(j),并将∀sum−len≤i≤sum-1,f(i)加1,
- 对于修改,可以通过差分维护,并记差分数组为f2(i)(定义f(i)=∑[j=min,i]f2(j) )
对于查询,不妨暴力枚举sum,同样利用sum变化的连续性,线性维护∑[j=min,sum]f2(j)和∑[j=min,sum−1]f(j),那么不难得到查询的结果(sum暴力枚举即代替了i),但时间复杂度仍为o(n^2)
————
Part3
- 考虑min,显然当sum≤min时之后的部分必然都为0,不妨直接变为最终的sum−l(注意更新min)
- 考虑此时的复杂度,注意到每一次mn减少,都会减少对应sum的枚举,而最终mn至多为所有元素和,因此sum的枚举级别是1的个数,也即o(n)的。对于单个1直接暴力"枚举"sum即可,显然是o(n)的。最终,总复杂度为o(n),可以通过
//AC1
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 2e6+10;
vector<int>v[maxn];//记录每个众数出现的位置
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int T; cin>>T;
while(T--){
int n; cin>>n;
vector<int>a(n+1);
unordered_set<int>s; //记录有哪些众数
for(int i=1; i <= n; i++){
cin>>a[i]; v[a[i]].push_back(i); s.insert(a[i]);
}
LL ans = 0;
for(int x : s){//枚举每个数作为众数
LL res = 0, sum = 0;//x的答案贡献res,当前前缀和sum
unordered_map<int,int>f1,f2; //前缀和为sum的点有f1[sum]个, f2为f1差分数组
v[x].push_back(n+1); //防止溢出
LL k = 0, minn = 0; //枚举众数的每个位置k,历史前缀最小值minn
for(int j = 1; j <= n; j++){//枚举每一段-1的起始位置
if(j > v[x][k])k++;
if(a[j]!=x && sum==minn){//区间[j,v[x][k]-1]都是-1
LL len = v[x][k]-1-j;
f2[sum-len]++; f2[sum+1]--;//区间f1[sum−x,sum-1]+=1
j += len;
sum -= len+1;
}else if(a[j]==x){ //更新答案
f1[sum]+=f2[sum];//对差分数组求前缀和并更新到f1中去
f2[sum+1]+=f2[sum];
f2[sum]=0;
f1[sum]++;//当前前缀和的个数++
res += f1[sum];//累加为∑[0,sum−1]f(i)
sum++;
ans += res;
}else{
f1[sum]++; sum--;
res -= f1[sum];
ans += res;
}
minn = min(minn, sum);
}
}
cout<<ans<<"\\n";
for(auto &i: s)v[i].clear();
}
return 0;
}
//AC2
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int maxn = 1e6+10;
LL ans;
vector<int>v[maxn];
int f[maxn<<1], cnt, sum, mn, s;
stack<int>st;
void update(int l, int r){
f[l]++; f[r+1]--;
st.push(l), st.push(r+1);
cnt++;
}
void dec(int x){
for(int i=0; i < x; i++){
if(sum==mn){
sum=mn=sum-(x-i);
cnt=s=0;
return ;
}
cnt -= f[sum--], s-=cnt, ans+=s;
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int T; cin>>T;
while(T--){
int n, m=1e6; ans=0;
cin>>n;
for(int i = 0; i <= m; i++)v[i].clear();
for(int i = 1; i <= n; i++){
int x; cin>>x; v[x].push_back(i);
}
for(int i = 0; i <= m; i++){//枚举每个数作为众数
int lst = 0;
sum = mn = n; cnt=s=0;
update(sum,sum);
for(int x : v[i]){//枚举该数的位置
if(lst+1 < x){//中间都是-1
dec(x-lst-1);
update(sum,sum+(x-lst-1)-1);
}
s += cnt, cnt += f[++sum], ans += s;
update(sum,sum);
lst = x;
}
if(lst < n)dec(n-lst);
while(!st.empty()){
f[st.top()]=0; st.pop();
}
}
cout<<ans<<"\\n";
}
return 0;
}
以上是关于2021杭电多校赛2021“MINIEYE杯”中国大学生算法设计超级联赛签到题4题的主要内容,如果未能解决你的问题,请参考以下文章
2021杭电多校赛2021“MINIEYE杯”中国大学生算法设计超级联赛签到题4题
2021杭电多校赛2021“MINIEYE杯”中国大学生算法设计超级联赛签到题5题
2021杭电多校赛2021“MINIEYE杯”中国大学生算法设计超级联赛签到题5题
2021杭电多校赛2021“MINIEYE杯”中国大学生算法设计超级联赛签到题2题