一些感想

感觉最近很不在状态,整场比赛不知道在干什么... 连 \(\rm C\) 这么水的暴力也没看出来...

真的应该改改自己想完就开码的坏习惯,不能因为是虚拟赛就这么随意。

\(\text{C - Manhattan Subarrays}\)

解法

考虑 \(d(p,r)=|x_p-x_r|+|y_p-y_r|\)\(d(p,q)+d(q,r)=(|x_p-x_q|+|x_q-x_r|)+(|y_p-y_q|+|y_q-y_r|)\)

显然我们有 \(|x_p-x_r|\ge |x_p-x_q|+|x_q-x_r|\)\(y\) 同理。

所以当 \(d(p,r)=d(p,q)+d(q,r)\) 时必然有 \(|x_p-x_r|= |x_p-x_q|+|x_q-x_r|\)\(y\) 同理。而不会有一个大于一个小于的情况。

转化一下式子,发现实际上 "\(\text{bad triple}\)" 就是下标 \(i<j<k\),有 \(a_i\le a_j\le a_k\)\(a_i\ge a_j\ge a_k\)

"\(\text{good array}\)" 长度不超过 \(4\),汝可模拟得之。

简单证明一下。首先,可能的长度为 \(3\) 的 "\(\text{good array}\)" 只有两种情况(情况不同主要的是相对位置不同,对称的是等价的):

  1. ---
          ---
       ---
    
  2.       ---
    ---
       ---
    

对于 情况 1 没有长度为 \(4\) 的 "\(\text{good array}\)";而 情况 2 可以在 \(i,k\)\(i,j\) 中再插入一段,但这样必定又构造出 情况 1,所以结论得证。

代码

#include <cstdio>
#include <iostream>
using namespace std;

#define print(x,y) write(x),putchar(y) 

template <class T> inline T read(const T sample) {
    T x=0; int f=1; char s;
    while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
    while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
    return x*f;
}
template <class T> inline void write(const T x) {
    if(x<0) return (void) (putchar('-'),write(-x));
    if(x>9) write(x/10);
    putchar(x%10^48);
}

const int maxn=2e5+5;

int n,a[maxn];

bool ok(int l,int r) {
	if(r-l+1<=2) return 1;
	if(r-l+1==3) 
		return !((a[l]<=a[l+1] and a[l+1]<=a[r])or
				(a[l]>=a[l+1] and a[l+1]>=a[r]));
	for(int i=l;i<=l+1;++i)
		for(int j=i+1;j<r;++j)
			for(int k=j+1;k<=r;++k)
				if((a[i]<=a[j] and a[j]<=a[k])or
				(a[i]>=a[j] and a[j]>=a[k])) 
					return 0;
	return 1;
	
}

int main() {
	int ans,T=read(9);
	for(int o=1;o<=T;++o) {
		n=read(9); ans=0;
		for(int i=1;i<=n;++i)
			a[i]=read(9);
		for(int i=1;i<=n;++i) {
			for(int j=i;j<=min(i+3,n);++j)
				if(ok(i,j)) ++ans;
				else break;
		}
		print(ans,'\n');
	}
	return 0;
} 

\(\text{E - Stringforces}\)

解法

\(k\le 17\),我们考虑状压。一个基本的转化是二分最大值 \(x\),那么就只用检查每种字母是否均有长度为 \(x\) 的段。

\(dp_s\) 为满足的字母集合为 \(s\) 需要的最短前缀。考虑转移,需要计算在位置 \(i\) 之后构造出字母 \(j\) 的长度为 \(x\)最靠前 段的右端点。

直接在 \(dp_s\) 转移时计算此信息会导致复杂度飙升,其实可以直接预处理,令它为 \(pos_{i,j}\)

对于 \(pos_{i,j}\) 的转移,显然如果 \([i+1,i+x]\) 中没有其他字母就可以更新为 \(i+x\),否则直接继承 \(pos_{i+1,j}\)

关于判定 \([i+1,i+x]\) 中是否有其他字母,可以将字母正反都枚举一遍,具体可以看看代码;还可以直接对每个字母做一个出现次数的后缀和,再统计所有字母出现次数后缀和。反正都是 \(\mathcal O(nk)\) 的。

代码

#include <cstdio>
#include <iostream>
using namespace std;

#define print(x,y) write(x),putchar(y) 

template <class T> inline T read(const T sample) {
    T x=0; int f=1; char s;
    while((s=getchar())>'9'||s<'0') if(s=='-') f=-1;
    while(s>='0'&&s<='9') x=(x<<1)+(x<<3)+(s^48),s=getchar();
    return x*f;
}
template <class T> inline void write(const T x) {
    if(x<0) return (void) (putchar('-'),write(-x));
    if(x>9) write(x/10);
    putchar(x%10^48);
}

const int maxn=2e5+5;

int n,k,pos[maxn][18];
int dp[1<<17],las[18];
char s[maxn];

bool ok(int mid) {
	for(int i=0;i<k;++i) 
		las[i]=n+1,
		pos[n][i]=n+1;
	for(int i=n-1;i>=0;--i) {
		if(s[i]^'?') 
			las[s[i]-'a']=i+1;
		int cur=n+1;
		for(int j=0;j<k;++j) {
			pos[i][j]=(i+mid<cur?i+mid:pos[i+1][j]);
			cur=min(cur,las[j]);
		}
		cur=n+1;
		for(int j=k-1;j>=0;--j) {
			if(i+mid>=cur)
				pos[i][j]=pos[i+1][j];
			cur=min(cur,las[j]);
		}
	}
	for(int s=0;s<(1<<k);++s) dp[s]=n+1;
	dp[0]=0;
	for(int s=0;s<(1<<k);++s)
		if(dp[s]<n)
			for(int i=0;i<k;++i)
				if(!(s>>i&1))
					dp[s|(1<<i)]=min(dp[s|(1<<i)],pos[dp[s]][i]);
	return dp[(1<<k)-1]<=n;
}

int main() {
	n=read(9),k=read(9);
	scanf("%s",s);
	int l=0,r=n,mid;
	while(l^r) {
		mid=l+r+1>>1;
		if(ok(mid))
			l=mid;
		else r=mid-1;
	}
	print(l,'\n');
	return 0;
}
posted on 2021-07-17 08:37  Oxide  阅读(39)  评论(0编辑  收藏  举报