20240724模拟赛订正题笔记
(T1)lnsyoj2208 逆流而上/P10737 [SEERC2020] Reverse Game
考虑到失败时字符串应为前面都是0,后面都是1(例如"0000001111111")
所以可以将原串的逆序对数求出,记为m,对于每个可翻转的串进行分类讨论:
1."10"->"01"可以将原串的逆序对减1。
2."100"->"001" "110"->"011" "1010"->"0101"可以将原串的逆序对减2。
综上所述,该问题变成了取石子问题。
所以当m%3==0时后手(B)胜,否则先手(A)胜。
代码如下:
#include <cstdio>
#include <cstring>
#define int long long
using namespace std;
int cnt1;
int T;
int n;
char s[1000005];
signed main(){
scanf("%lld",&T);
while(T--){
scanf("%s",s+1);
n=strlen(s+1);
cnt1=0;
int res=0;
for(int i=1;i<=n;i++){
if(s[i]=='1'){
cnt1++;
}
else if(s[i]=='0'){
res+=cnt1;
}
}
if(res%3==0){
printf("B\n");
}
else{
printf("A\n");
}
}
return 0;
}
(T2)lnsyoj2209 帝国飘摇/P10455 Genius Acm
首先若固定一组零件的左端点,则右端点越靠右越好,所以选择贪心。
对于一组零件内,可以一个指针从大到小选数,另一个指针从小到大选数,这样即可以使val最大。
所以可以倍增枚举右端点,枚举时进行排序,时间复杂度\(O(nlog^{2}n)\),可拿90pts。
排序时可以不排整个一组,可以只排新增加进的零件,然后原先一组和新进的零件都是有序的,所以可以\(O(n)\)合并,所以时间复杂度优化成了\(O(nlogn)\),可拿100pts。
代码如下:
#include <cstdio>
#include <algorithm>
#define int long long
using namespace std;
int T;
int n,m,k;
struct vi{
int data;
}vis[500005],gbvis[500005],yvis[500005];
bool cmp1(vi a,vi b){
return a.data<b.data;
}
signed main(){
scanf("%lld",&T);
while(T--){
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1;i<=n;i++){
scanf("%lld",&yvis[i].data);
}
int lr=0;
int ans=0;
for(int l=1,r=0,base=0;l<=n;base++){
if(base==-1){
l=r+1;
base=0;
ans++;
}
lr=r;
r+=1<<base;
if(r>n){
base-=2;
r=lr;
}
for(int i=lr+1;i<=r;i++){
vis[i].data=yvis[i].data;
}
sort(vis+lr+1,vis+r+1,cmp1);
if(l<=lr){
int i1=l,j1=lr+1,k1=l;
while(i1<=lr && j1<=r){
if(vis[i1].data<vis[j1].data){
gbvis[k1].data=vis[i1].data;
k1++;
i1++;
}
else{
gbvis[k1].data=vis[j1].data;
k1++;
j1++;
}
}
while(i1<=lr){
gbvis[k1].data=vis[i1].data;
k1++;
i1++;
}
while(j1<=r){
gbvis[k1].data=vis[j1].data;
k1++;
j1++;
}
}
else{
for(int i=l;i<=r;i++){
gbvis[i].data=vis[i].data;
}
}
int x=r-l+1;
int val=0;
if(x<2*m){
for(int i=1;i<=x/2;i++){
val+=(gbvis[l+i-1].data-gbvis[r-i+1].data)*(gbvis[l+i-1].data-gbvis[r-i+1].data);
}
}
else{
for(int i=1;i<=m;i++){
val+=(gbvis[l+i-1].data-gbvis[r-i+1].data)*(gbvis[l+i-1].data-gbvis[r-i+1].data);
}
}
if(val>k){
base-=2;
r=lr;
}
else{
for(int i=l;i<=r;i++){
vis[i].data=gbvis[i].data;
}
}
}
printf("%lld\n",ans);
}
return 0;
}
(T3)lnsyoj2210 致命冲击/P5069 [Ynoi2015] 纵使日薄西山
此题我们可以利用线段树维护此序列的权值,具体如下:
在一个操作中,若选择了一个数,则这个数两边的数不能被选。(证明:因为若选择此数,则必须满足此数大于这个数两边的数,通过不等式的性质得知这个数两边的数在之后的操作中也不能被选)
可已将此序列拆成若干序列,通过上个结论可以使线段树具有可合并性,所以对这些数列进行合并,合并过程充分利用了pushup。
两个序列合并一共分为两种情况:
1.左序列最右的数被选 且 右序列最左的数被选:
此时选两个数中最大的数,另一个数强制不被选。
2.左序列最右的数没被选 或 右序列最左的数没被选 或 两个数都没被选:
此时直接合并即可。
所以线段树需要维护:(左端点:序列最左的元素 右端点:左端点:序列最右的元素)
1.当序列左右端点都可以被选时序列的权值
2.当序列左端点强制不被选 且 右端点可以被选 时序列的权值
3.当序列右端点强制不被选 且 左端点可以被选 时序列的权值
4.当序列左端点强制不被选 且 右端点强制不被选 时序列的权值
5.当序列左右端点都可以被选时序列左端点是否被选中
6.当序列左端点强制不被选 且 右端点可以被选 时序列左端点是否被选中
7.当序列右端点强制不被选 且 左端点可以被选 时序列左端点是否被选中
8.当序列左端点强制不被选 且 右端点强制不被选 时序列左端点是否被选中
9.当序列左右端点都可以被选时序列右端点是否被选中
10.当序列左端点强制不被选 且 右端点可以被选 时序列右端点是否被选中
11.当序列右端点强制不被选 且 左端点可以被选 时序列右端点是否被选中
12.当序列左端点强制不被选 且 右端点强制不被选 时序列右端点是否被选中
13.序列左端点的大小
14.序列右端点的大小
pushup函数需要对以上所有进行维护
代码如下:
#include <cstdio>
#define int long long
using namespace std;
struct node{
int data[4];//0:YY 1:YN 2:NY 3:NN
bool xl[4];
bool xr[4];
int shul;
int shur;
}ns[400005];
int n,q,a[100005];
inline void pushup(int id){
if(ns[id*2].xr[0] && ns[id*2+1].xl[0]){
if(ns[id*2].shur>=ns[id*2+1].shul){
ns[id].xl[0]=ns[id*2].xl[0];
ns[id].xr[0]=ns[id*2+1].xr[2];
ns[id].data[0]=ns[id*2].data[0]+ns[id*2+1].data[2];
}
else{
ns[id].xl[0]=ns[id*2].xl[1];
ns[id].xr[0]=ns[id*2+1].xr[0];
ns[id].data[0]=ns[id*2].data[1]+ns[id*2+1].data[0];
}
}
else{
ns[id].xl[0]=ns[id*2].xl[0];
ns[id].xr[0]=ns[id*2+1].xr[0];
ns[id].data[0]=ns[id*2].data[0]+ns[id*2+1].data[0];
}
if(ns[id*2].xr[0] && ns[id*2+1].xl[1]){
if(ns[id*2].shur>=ns[id*2+1].shul){
ns[id].xl[1]=ns[id*2].xl[0];
ns[id].xr[1]=ns[id*2+1].xr[3];
ns[id].data[1]=ns[id*2].data[0]+ns[id*2+1].data[3];
}
else{
ns[id].xl[1]=ns[id*2].xl[1];
ns[id].xr[1]=ns[id*2+1].xr[1];
ns[id].data[1]=ns[id*2].data[1]+ns[id*2+1].data[1];
}
}
else{
ns[id].xl[1]=ns[id*2].xl[0];
ns[id].xr[1]=ns[id*2+1].xr[1];
ns[id].data[1]=ns[id*2].data[0]+ns[id*2+1].data[1];
}
if(ns[id*2].xr[2] && ns[id*2+1].xl[0]){
if(ns[id*2].shur>=ns[id*2+1].shul){
ns[id].xl[2]=ns[id*2].xl[2];
ns[id].xr[2]=ns[id*2+1].xr[2];
ns[id].data[2]=ns[id*2].data[2]+ns[id*2+1].data[2];
}
else{
ns[id].xl[2]=ns[id*2].xl[3];
ns[id].xr[2]=ns[id*2+1].xr[0];
ns[id].data[2]=ns[id*2].data[3]+ns[id*2+1].data[0];
}
}
else{
ns[id].xl[2]=ns[id*2].xl[2];
ns[id].xr[2]=ns[id*2+1].xr[0];
ns[id].data[2]=ns[id*2].data[2]+ns[id*2+1].data[0];
}
if(ns[id*2].xr[2] && ns[id*2+1].xl[1]){
if(ns[id*2].shur>=ns[id*2+1].shul){
ns[id].xl[3]=ns[id*2].xl[2];
ns[id].xr[3]=ns[id*2+1].xr[3];
ns[id].data[3]=ns[id*2].data[2]+ns[id*2+1].data[3];
}
else{
ns[id].xl[3]=ns[id*2].xl[3];
ns[id].xr[3]=ns[id*2+1].xr[1];
ns[id].data[3]=ns[id*2].data[3]+ns[id*2+1].data[1];
}
}
else{
ns[id].xl[3]=ns[id*2].xl[2];
ns[id].xr[3]=ns[id*2+1].xr[1];
ns[id].data[3]=ns[id*2].data[2]+ns[id*2+1].data[1];
}
ns[id].shul=ns[id*2].shul;
ns[id].shur=ns[id*2+1].shur;
}
void build(int id,int l,int r){
if(l==r){
ns[id].data[0]=a[l];
ns[id].xl[0]=ns[id].xr[0]=true;
ns[id].shul=a[l];
ns[id].shur=a[l];
return;
}
int mid=(l+r)/2;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
pushup(id);
}
void set(int id,int l,int r,int wei,int zhi){
if(l==r){
ns[id].data[0]=zhi;
ns[id].shul=zhi;
ns[id].shur=zhi;
return;
}
int mid=(l+r)/2;
if(wei<=mid){
set(id*2,l,mid,wei,zhi);
}
else{
set(id*2+1,mid+1,r,wei,zhi);
}
pushup(id);
}
signed main(){
scanf("%lld%lld",&n,&q);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
build(1,1,n);
while(q--){
int x,y;
scanf("%lld%lld",&x,&y);
set(1,1,n,x,y);
printf("%lld\n",ns[1].data[0]);
}
return 0;
}
(T4)lnsyoj2211 通天之塔/P10652 「ROI 2017 Day 1」前往大都会
此题一共有两个问,可以分别求一下:
1.对于第一问,直接跑最短路即可。
2.对于第二问,可以使用dp解决,方法如下:
设\(f_i\)为到i点时e的和的最大值
可得\(f_i=f_j+(dis_i-dis_j)^2\)
利用斜率优化dp,经过化简得\(f_{i}-dis_{i}^{2}=f_{j}+dis_{j}^{2}-2dis_{i}dis_{j}\)
由此得\(\begin{cases}x=dis_{j}\\y=f_{j}+dis_{j}^{2}\\k=2dis_{i}\\b=f_{i}-dis_{i}^{2}\end{cases}\)
因为要使\(f_i\)最大,所以应该使\(b\)最大,所以维护上凸包。
考虑\(x\)和\(k\)均单调递增,所以维护单调栈。
对于本题需要先建一个最短路图(\(dis_{u}+w_{u,v}=dis_{v}\)的边所连成的图),然后对于每个火车线分别维护一个单调栈(坑点:对于在同一条火车线但被分成两段的边需要分别开栈)。
对于所有点需要按\(dis\)进行排序,保证\(x\)和\(k\)单调,然后对于每个点,先通过经过这个点的所有铁路线算出这个点的dp,再把这个点放到经过这个点的每个铁路所维护的凸包中。
最后得出答案。
(实际不用新建图,只需要记录一下每个点被哪条铁路线所经过即可)
代码如下:
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
struct Edge{
int next;
int to;
int w;
//int vi;
int u;
}es[2000005];
int head[1000005],cnt;
vector<int> vi[1000005];
vector<int> nr[1000005];
inline void add(int u,int v,int w,int vi1){
es[cnt].next=head[u];
es[cnt].to=v;
es[cnt].w=w;
es[cnt].u=u;
vi[vi1].push_back(cnt);
head[u]=cnt;
cnt++;
}
int n,m;
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > pq;
struct ys{
int dis;
int id;
}yss[1000005];
bool cmp1(ys a,ys b){
return a.dis<b.dis;
}
bool cmp2(ys a,ys b){
return a.id<b.id;
}
bool vis[1000005];
int dp[1000005];
vector<int> stas[1000005];
vector<int> cov[1000005];
inline int calc(int i,int j){//yss编号
return dp[yss[j].id]+(yss[i].dis-yss[j].dis)*(yss[i].dis-yss[j].dis);
}
inline long double slope(int a,int b){//yss编号
return 1.0*(dp[yss[a].id]+yss[a].dis*yss[a].dis-dp[yss[b].id]-yss[b].dis*yss[b].dis)/\
(long double)(yss[a].dis-yss[b].dis);
}
int cntv;
signed main(){
cnt=1;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++){
int l,u,v,w;
scanf("%lld",&l);
scanf("%lld",&u);
for(int j=1;j<=l;j++){
scanf("%lld",&w);
scanf("%lld",&v);
add(u,v,w,i);
u=v;
}
}
for(int i=1;i<=n;i++){
yss[i].dis=0x3f3f3f3f3f3f3f3f;
yss[i].id=i;
}
yss[1].dis=0;
pq.push({0,1});
while(!pq.empty()){
int u=pq.top().second;
pq.pop();
if(vis[u]){
continue;
}
vis[u]=true;
for(int i=head[u];i;i=es[i].next){
int v=es[i].to;
if(yss[u].dis+es[i].w<yss[v].dis){
yss[v].dis=yss[u].dis+es[i].w;
pq.push({yss[v].dis,v});
}
}
}
for(int i=1;i<=m;i++){
for(int j=0;j<vi[i].size();j++){
if(yss[es[vi[i][j]].u].dis+es[vi[i][j]].w==yss[es[vi[i][j]].to].dis){
nr[i].push_back(vi[i][j]);
}
}
}
for(int i=1;i<=m;i++){
if(!nr[i].empty()){
cntv++;
cov[es[nr[i][0]].u].push_back(cntv);
cov[es[nr[i][0]].to].push_back(cntv);
}
for(int j=1;j<nr[i].size();j++){
if(es[nr[i][j]].u!=es[nr[i][j-1]].to){
cntv++;
cov[es[nr[i][j]].u].push_back(cntv);
}
cov[es[nr[i][j]].to].push_back(cntv);
}
}
sort(yss+1,yss+n+1,cmp1);
for(int i=1;i<=n;i++){
for(int j=0;j<cov[yss[i].id].size();j++){
if(stas[cov[yss[i].id][j]].empty()){
continue;
}
while(stas[cov[yss[i].id][j]].size()>1 && \
calc(i,stas[cov[yss[i].id][j]][stas[cov[yss[i].id][j]].size()-1])<\
calc(i,stas[cov[yss[i].id][j]][stas[cov[yss[i].id][j]].size()-2])){
stas[cov[yss[i].id][j]].pop_back();
}
dp[yss[i].id]=max(dp[yss[i].id],calc(i,stas[cov[yss[i].id][j]].back()));
}
for(int j=0;j<cov[yss[i].id].size();j++){
while(stas[cov[yss[i].id][j]].size()>1 && \
slope(i,stas[cov[yss[i].id][j]][stas[cov[yss[i].id][j]].size()-1])>\
slope(stas[cov[yss[i].id][j]][stas[cov[yss[i].id][j]].size()-1],stas[cov[yss[i].id][j]][stas[cov[yss[i].id][j]].size()-2])){
stas[cov[yss[i].id][j]].pop_back();
}
stas[cov[yss[i].id][j]].push_back(i);
}
}
sort(yss+1,yss+n+1,cmp2);
printf("%lld %lld\n",yss[n].dis,dp[n]);
return 0;
}