2024.11.21 2024辽宁省赛
Solved:11/13
Penalty:1224
Rank:3
Rank(vp):5
没打过某两强校啊。。。
B. 比分幻术
签到,把输入的字符串反转输出。
#include<bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(0);cin.tie(0);
string s;
cin>>s;
cout<<s[2]<<s[1]<<s[0]<<'\n';
}
J. 结课风云
签到,依次统计改分前后的通过人数。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
const int N=1005;
int n,a,b,c,d,x[N],y[N];
int main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>a>>b>>c;
int cnt1=0,cnt2=0;
for(int i=1;i<=n;++i)cin>>x[i]>>y[i],cnt1+=x[i]+y[i]>=c;
cin>>d;
for(int i=1;i<=n;++i)x[i]=min(x[i]+d,a),cnt2+=x[i]+y[i]>=c;
cout<<cnt2-cnt1<<'\n';
}
A. 爱上字典
不需要写哈希,直接用 set<string>
实现字符串查找,不会超时。
#include<bits/stdc++.h>
using namespace std;
int n;
string s,t;
set<string> ss;
int main(){
ios::sync_with_stdio(0);cin.tie(0);
getline(cin,s),s+=' ';
cin>>n;
for(int i=0;i<n;++i){
cin>>t;
ss.insert(t);
}
n=ss.size();
int m=s.length();
ll h1=0,h2=0;
t="";
for(int i=0;i<m;++i){
char ch=s[i];
if(ch>='A'&&ch<='Z')ch+=32;
if(ch==' '){
if(ss.find(t)==ss.end())ss.insert(t);
t="";
}
if(ch>='a'&&ch<='z')t+=ch;
}
ss.erase("");
cout<<ss.size()-n<<'\n';
}
L. 龙之研习
题意:闰年规则改为 \(4\times 100^p\) 年一闰,\(100^p\) 年不闰。求2024年开始第 \(k\) 个平年。
二分答案,问题转化为算 \(n\) 年之前有多少平年。对 \(p\) 从小到大枚举直到 \(100^p>n\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
ll q,m;
ll chk(ll n){
ll ans=0;
for(ll r=1;r<=n;r*=100){
ans+=n/r;
ans-=n/(4*r);
}
return ans;
}
void solve(){
cin>>m;
ll l=m+2024,r=2*m+2024,mid,res;
while(l<=r){
mid=(l+r)>>1;
if(chk(mid)-q>=m)res=mid,r=mid-1;
else l=mid+1;
}
cout<<res<<'\n';
}
int main(){
q=chk(2024);
ios::sync_with_stdio(0);cin.tie(0);
int T;
cin>>T;
while(T--)solve();
}
C. 插排串联
题意:给一棵树,可以任意交换非根非叶子节点,问能否使得对每个非叶子节点,其子树中所有叶子节点权值之和小于等于自身权值。
题意等价于给每个非根非叶子节点分配一个大于等于某限制的权值。multiset存待取权值,然后贪心选取最小的满足限制的权值即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
const int N=1e5+5;
int n,x;
ll a[N];
vector<int> e[N];
bool fl=1;
void adde(int x,int y){
e[x].push_back(y);
}
multiset<ll> s;
void dfs(int u){
if(!e[u].size())return;
int sum=0;
for(int v:e[u])dfs(v),sum+=a[v];
if(!u)return;
auto it=s.lower_bound(sum);
if(it==s.end()){fl=0;return;}
a[u]=sum;
s.erase(it);
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n;
for(int i=1;i<=n;++i){
cin>>x>>a[i];
adde(x,i);
}
ll sum=0;
for(int i=1;i<=n;++i){
if(e[i].size())s.insert(a[i]);
else sum+=a[i];
}
if(sum>2200){cout<<"NO\n";return 0;}
dfs(0);
cout<<(fl?"YES":"NO")<<'\n';
}
E. 俄式简餐
题意:用 L 形和 I 形俄罗斯方块填充 \(n\times m\) 棋盘,给出方案。
格子数不能被 \(4\) 整除一定无解,另外 \(n=m=2\) 也无解。下面对剩余情况给出构造:
-
\(4|n\) 或 \(4|m\):用 I 形填满即可;
-
\(n,m\) 均不能被 \(4\) 整除:容易构造出 \(2\times 6\)(或者 \(6\times 2\))的解,因此对前 \(6\) 行反复用 \(6\times 2\) 填,剩下用 I 填满即可。注意特判 \(n=2\)(此时没有前 \(6\) 行)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pll;
const int N=1e5+5;
int n,m;
void solve(){
cin>>n>>m;
if(n*m&3){cout<<"NO\n";return;}
if(n==2&&m==2){cout<<"NO\n";return;}
cout<<"YES\n";
if(!(n&3)){
for(int i=0;i<n;++i,cout<<'\n')
for(int j=0;j<m;++j)
cout<<(i>>2)*m+j+1<<' ';
}
else if(!(m&3)){
for(int i=0;i<n;++i,cout<<'\n')
for(int j=0;j<m;++j)
cout<<i*(m>>2)+(j>>2)+1<<' ';
}
else{
vector<vector<int>> a(n);
for(int i=0;i<n;++i)a[i].resize(m);
if(n==2){
a[0][0]=a[0][1]=a[0][2]=a[1][0]=1;
a[0][3]=a[0][4]=a[0][5]=a[1][5]=2;
a[1][1]=a[1][2]=a[1][3]=a[1][4]=3;
int cnt=3;
for(int i=6;i<m;i+=4){
a[0][i]=a[0][i+1]=a[0][i+2]=a[0][i+3]=++cnt;
a[1][i]=a[1][i+1]=a[1][i+2]=a[1][i+3]=++cnt;
}
}
else{
int cnt=0;
for(int i=0;i<m;i+=2){
a[0][i]=a[1][i]=a[2][i]=a[0][i+1]=++cnt;
a[3][i]=a[4][i]=a[5][i]=a[5][i+1]=++cnt;
a[1][i+1]=a[2][i+1]=a[3][i+1]=a[4][i+1]=++cnt;
}
for(int i=6;i<n;i+=4)
for(int j=0;j<m;++j)
a[i][j]=a[i+1][j]=a[i+2][j]=a[i+3][j]=++cnt;
}
for(int i=0;i<n;++i,cout<<'\n')
for(int j=0;j<m;++j)
cout<<a[i][j]<<' ';
}
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
int T;
cin>>T;
while(T--)solve();
}
G. 顾影自怜
题意:给一个序列,问有多少连续子列的最大值出现次数 \(\geq k\)。
预处理每个值的所有出现位置(可以用vector数组)\(c_{i,j}\)。
首先枚举最大值 \(x\) 及其第一次出现的位置 \(c_{x,i}\),则它第 \(k\) 次出现的位置为 \(c_{x,i+k-1}\)。
二分子列左端点的最左位置 \(L\) 和右端点的最右位置 \(R\)。分别需要满足 \(\max(a[L...c_{x,i}])=x\) 和 \(\max(a[c_{x,i+k-1}...R])=x\)。区间max可以用st表 \(O(n\log n)~O(1)\) 算。
总复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
int n,k,a[N],st[20][N];
vector<int> pos[N];
int qry(int l,int r){
int o=__lg(r-l+1);
return max(st[o][l],st[o][r-(1<<o)+1]);
}
void solve(){
cin>>n>>k;
for(int i=1;i<=n;++i)cin>>a[i],pos[a[i]].push_back(i),st[0][i]=a[i];
for(int i=1;1<<i<=n;++i)
for(int j=1;j+(1<<i)-1<=n;++j)
st[i][j]=max(st[i-1][j],st[i-1][j+(1<<i-1)]);
ll ans=0;
for(int i=1;i<=n;++i){
for(int j=0;j+k-1<pos[i].size();++j){
int lp=pos[i][j],rp=pos[i][j+k-1];
if(qry(lp,rp)>i)continue;
int l=j?pos[i][j-1]+1:1,r=lp,mid,L=lp,R=rp;
while(l<=r){
mid=(l+r)>>1;
if(qry(mid,lp)==i)r=mid-1,L=mid;
else l=mid+1;
}
l=rp,r=n;
while(l<=r){
mid=(l+r)>>1;
if(qry(rp,mid)==i)l=mid+1,R=mid;
else r=mid-1;
}
ans+=1ll*(lp-L+1)*(R-rp+1);
}
}
cout<<ans<<'\n';
for(int i=1;i<=n;++i)pos[i].clear();
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
int T;
cin>>T;
while(T--)solve();
}
D. 都市叠高
题意:给一列二维平面上的点,将它们划分为若干区间,使得每个区间的点形成的凸包直径之和最大。
这是一道套着计算几何的皮的简单dp题(
点集凸包的直径一定是最远点对的距离。而在最优划分方案中,我们一定会把最远的两个点分到区间的左右端点(否则将这个区间拆成两段或者三段一定更优)。
因此有转移 \(f_i = \max_{j<i}\{f_{j-1} + d(a_i,a_j)\}\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5005;
int n,x[N],y[N];
double f[N];
double dis(int i,int j){
return sqrt(1.*(x[i]-x[j])*(x[i]-x[j])+1.*(y[i]-y[j])*(y[i]-y[j]));
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n;
for(int i=1;i<=n;++i)cin>>x[i]>>y[i];
for(int i=1;i<=n;++i){
f[i]=f[i-1];
for(int j=1;j<i;++j)
f[i]=max(f[i],f[j-1]+dis(i,j));
}
cout<<fixed<<setprecision(12)<<f[n]<<'\n';
}
K. 可重集合
题意:维护一个可重集 \(S\),支持插入和删除,并查询从集合中选取任意多个数相加能得到不同的和的数量。\(n\leq 5000,\sum(S)\leq 5\times 10^5\)。
如果没有删除操作,直接用 bitset 维护即可做到 \(O(\frac{ns}w)\)。
直接删除是不能用 bitset 做的(这等价于从 01 背包里删一个物品)。
每个物品的存在时间是一个区间,因此可用线段树分治将“删除一个物品”转化为“撤销加入一个物品”(后者直接把原来的 bitset 状态存下来即可)。
复杂度 \(O(\frac {ns\log n}w)\)。常数很小。
注:这个套路同样适用于一般的动态 01 背包问题。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5005,M=5e5+5;
int n,m=5e5,op[N],x[N];
vector<int> tr[N*4],s[M];
bitset<M> now;
#define lc (x<<1)
#define rc (x<<1|1)
void insert(int x,int l,int r,int L,int R,int val){
if(l==L&&r==R){
tr[x].push_back(val);
return;
}
int mid=(l+r)>>1;
if(R<=mid)insert(lc,l,mid,L,R,val);
else if(L>mid)insert(rc,mid+1,r,L,R,val);
else insert(lc,l,mid,L,mid,val),insert(rc,mid+1,r,mid+1,R,val);
}
int ans[N];
void calc(int x,int l,int r){
bitset<M> tmp=now;
for(int val:tr[x])now|=now<<val;
if(l==r){ans[l]=now.count()-1,now=tmp;return;}
int mid=(l+r)>>1;
calc(lc,l,mid),calc(rc,mid+1,r);
now=tmp;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n;
for(int i=1;i<=n;++i)cin>>op[i]>>x[i];
for(int i=1;i<=n;++i){
if(op[i]==1)s[x[i]].push_back(i);
else insert(1,1,n,s[x[i]].back(),i-1,x[i]),s[x[i]].pop_back();
}
for(int i=1;i<=m;++i)
for(int j:s[i])insert(1,1,n,j,n,i);
now[0]=1,calc(1,1,n);
for(int i=1;i<=n;++i)cout<<ans[i]<<'\n';
}
M. 盲盒谜题
题意:大模拟,略。
直接按题意暴力做就好了。复杂度 \(O(9^2k)\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=1e5+5;
int n,m,k,t,a[N],ans[N];
vector<array<int,3>> lines={
{1,2,9},{1,3,8},{1,4,7},{1,5,6},
{2,3,4},{2,5,7},{4,6,9},{7,8,9}
};
int b[10];
bool empty(){
for(int i=1;i<=9;++i)if(~b[i])return 0;
return 1;
}
int first_empty(){
for(int i=1;i<=9;++i)if(!~b[i])return i;
return -1;
}
pii first_same(){
for(int i=1;i<=9;++i)if(~b[i])
for(int j=i+1;j<=9;++j)if(~b[j])
if(b[i]==b[j])return {i,j};
return {-1,-1};
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>m>>k>>t;
for(int i=1;i<=k;++i)cin>>a[i];
memset(b,-1,sizeof(b));
int l=1,cnt=n;
bool end=0;
while(!end){
end=1;
bool sp=0;
for(int i=1;i<=9;++i)if(!~b[i]){
if(l>min(k,cnt))break;
end=0;
if(a[l]==0){
++l,++cnt,sp=1;
break;
}
else{
b[i]=a[l];
if(a[l]==t)++cnt;
++l;
}
}
if(sp)continue;
if(!~first_empty()&&first_same()==pii(-1,-1)){
end=0,b[1]=-1;
}
sp=0;
for(int i=0;i<8;++i)if(~b[lines[i][0]]&&b[lines[i][0]]==b[lines[i][1]]&&b[lines[i][1]]==b[lines[i][2]]){
end=0;
if(lines[i][0]>1)b[lines[i][0]]=-1;
else sp=1;
b[lines[i][1]]=b[lines[i][2]]=-1;
cnt+=5;
}
pii t;
while((t=first_same())!=pii(-1,-1)){
end=0;
if(t.first>1)b[t.first]=-1;
else sp=1;
b[t.second]=-1;
++cnt;
}
if(sp)b[1]=-1;
if(end)break;
if(empty())cnt+=10;
}
for(int i=1;i<=min(cnt,k);++i)++ans[a[i]];
for(int i=0;i<=m;++i)cout<<ans[i]<<' ';
cout<<'\n';
if(k<cnt)cout<<"Unhappy! "<<cnt-k<<'\n';
}
I. 野兽节拍
题意:给一个字符串 \(S\),你可以任选一个长度为3的模式串 \(T\) 并不断消除 \(S\) 中出现的模式串直到无法消除为止。求最多的次数以及此时选择的 \(T\)。
注意到所有模式串的消除次数之和是 \(O(n)\) 级别的,因此只需快速维护消除过程即可。
消除有两种情况,一是本身就有模式串作为子串,二是之前的消除产生了连锁反应。连锁反应只可能在上次消除的周围三个位置产生。
预处理每个模式串初始的出现位置,用链表维护当前的字符串。维护一个匹配指针,每次消除更新链表的同时枚举当前指针后面的三个位置,如果没找到模式串就说明这个位置的连锁反应已经结束了,直接把匹配指针移到下一个初始位置。
总复杂度 \(O(\sum ans(T))\)。事实上(官方题解)可以证明 \(\sum ans(T)\leq 20n\)。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int N=1e6+5;
int n,pre[N],nxt[N];
vector<pii> buf;
vector<int> pos[26][26][26];
void ins(int x,int y){
pre[x]=y,nxt[x]=nxt[y];
pre[nxt[y]]=x,nxt[y]=x;
}
void del(int x){
buf.push_back(pii(x,pre[x]));
pre[nxt[x]]=pre[x];
nxt[pre[x]]=nxt[x];
}
string a;
int ans;
vector<int> anss;
int calc(int x,int y,int z){
int i=pos[x][y][z][0],j=0,m=pos[x][y][z].size(),res=0;
while(j<m){
bool fl=0;
for(int k=0;k<3&&i<=n;++k){
if(pre[i]>0&&pre[pre[i]]>0&&a[i]==z&&a[pre[i]]==y&&a[pre[pre[i]]]==x){
fl=1,i=nxt[i],++res;
del(pre[i]),del(pre[i]),del(pre[i]);
break;
}
i=nxt[i];
}
if(!fl){
while(j<m&&pos[x][y][z][j]<i)++j;
if(j<m)i=pos[x][y][z][j];
}
}
for(int i=buf.size()-1;i>=0;--i)ins(buf[i].first,buf[i].second);
buf.clear();
return res;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);
cin>>n>>a,a=" "+a;
for(int i=1;i<=n;++i)a[i]-='a';
for(int i=0;i<=n+1;++i)pre[i]=i-1,nxt[i]=i+1;
for(int i=3;i<=n;++i)pos[a[i-2]][a[i-1]][a[i]].push_back(i);
for(int i=0;i<26;++i)
for(int j=0;j<26;++j)
for(int k=0;k<26;++k)if(pos[i][j][k].size()){
int t=calc(i,j,k);
if(t>ans)ans=t,anss={i,j,k};
}
cout<<ans<<'\n'<<char(anss[0]+'a')<<char(anss[1]+'a')<<char(anss[2]+'a')<<'\n';
}