2022/8/20 总结

A.P4398 [JSOI2008]Blue Mary的战役地图

  • 考场写了个暴力,我还以为要挂了,结果这题暴力可过

Solution

  • 本来想写 \(\mathtt{O(n^6)}\) 的暴力,但感觉可能过不了,所以加了亿点小优化;

  • 我把第一张地图(记为 \(a\))里的所有数字进行了离散化,开了一个 \(\mathtt{vector}\) 来存每个值在第二张地图(记为 \(b\))里对应的坐标,就避免了 \(\mathtt{n^4}\) 的枚举;

  • 然后枚举 \(a\) 中正方形的左上角坐标,枚举在 \(b\) 中对应的点坐标,再枚举正方形边长。一旦出现越界或是不同就弹出。

  • 最后加了一个小优化,就是当当前枚举的 \(a\) 坐标已经不足以产生一个边长大于 \(ans\) 的正方形时,直接弹出;

AC code
#include<bits/stdc++.h>
using namespace std;

inline int read(){
	int s=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(isdigit(ch)){
		s=s*10+int(ch-'0');
		ch=getchar();
	}
	return s*f;
}

const int N=55;

#define re register

struct memr{
	int x,y;
};

int n;
int a[N][N],b[N][N];
int c[N*N],tot=0;
int cl;
vector<memr>nxt[N*N];

inline int rk(int x){
	return lower_bound(c+1,c+cl+1,x)-c;
}

int main(){
//	freopen("map.in","r",stdin);
//	freopen("map.out","w",stdout);
	n=read();
	for(re int i=1;i<=n;++i)
		for(re int j=1;j<=n;++j){
			a[i][j]=read();
			c[++tot]=a[i][j];
		}
	sort(c+1,c+tot+1);
	cl=unique(c+1,c+tot+1)-c-1;
	for(re int i=1;i<=n;++i)
		for(re int j=1;j<=n;++j){
			b[i][j]=read();
			nxt[rk(b[i][j])].push_back((memr){i,j});
		}
	int ans=0;
	for(re int i=1;i<=n;++i){
		for(re int j=1;j<=n;++j){
			int r=rk(a[i][j]);
			for(re int k=0;k<nxt[r].size();++k){
				int x=nxt[r][k].x;
				int y=nxt[r][k].y;
				int cnt=0;
				bool s=1;
				while(s){
					++cnt;
					if(x+cnt-1>n || y+cnt-1>n) break;
					if(i+cnt-1>n || j+cnt-1>n) break;
					for(re int p=x,q=i;p<x+cnt;++p,++q)
						if(a[q][j+cnt-1]!=b[p][y+cnt-1]){
							s=0;
							break;
						}
					for(re int p=y,q=j;p<y+cnt-1 && s;++p,++q)
						if(a[i+cnt-1][q]!=b[x+cnt-1][p]){
							s=0;
							break;
						}<details>
<summary>点击查看代码</summary>

</details>
				}
				ans=max(ans,cnt-1);
			}
			if(ans>n-j+1)
				break;
		}
		if(ans>n-i+1)
			break;
	}
	printf("%d",ans);
	return 0;
}

B.P5770 [JSOI2016]无界单词

  • 考场上试图使用数论 \(AC\) 此题的我……

Solution

  • 求方案数其实有公式: \(f_i=2f_{i-1}-(i\%2==0)\times f_{i/2}\)
    不过 \(\mathtt{DP}\) 也可以,而且比较方便后续操作:\(f_i=2^i-\sum\limits_{j=1}^{i/2}\times 2^{i-2j}\)

  • 求具体字符串,每次确定一个字符,先设为 '\(a\)',再计算此条件下无界单词的个数,如果 \(<k\),则说明这一位为 '\(b\)',修改 \(k\) 后继续;

  • 快读写了个 \(\mathtt{int}\)\(x<<64=x<<0\) 直接卡了我 \(\mathtt{50pts}\)

AC code
#include<bits/stdc++.h>
using namespace std;

#define ll long long

int T,n;
ll k;
ll f[70];

void pre(){
	f[1]=f[2]=2;
	for(int i=3;i<=64;++i)
		f[i]=(1ll*f[i-1]<<1)-!(i%2)*f[i/2];
	return ;
}

ll ksm(int y){
	ll cnt=1,dx=2;
	for(;y;y>>=1,dx*=dx)
		if(y&1)
			cnt*=dx;
	return cnt;
}

ll count(ll x){
	ll cnt;
	if(x<f[n]>>1)
		cnt=(x+1)>>1;
	else cnt=(x+2)>>1;
	for(int i=2;i<=(n>>1);++i){
		ll d=x>>(n-i);
		d=(d+1)>>1;
		if(d<(1ll*1<<(i-1))) d=d>>1;
		else d=((d-(1ll*1<<(i-1)))>>1)+(1ll*1<<(i-2));
		cnt-=d;
	}
	return cnt;
}

ll find(){
	ll l=1,r=(1ll*1<<n)-2;
	while(l<r){
		ll mid=(l+r)>>1;
		if(count(mid)==k)
			return mid;
		if(count(mid)<k)
			l=mid+1;
		else if(count(mid)>k)
			r=mid-1;
	}
	return l;
}

void get(ll x){
	string str="";
	for(int i=n;i;--i)
		str+=char('a'+((x>>(i-1))&1));
	printf("%s\n",str.c_str());
	return ;
}

int main(){
//	freopen("word.in","r",stdin);
//	freopen("word.out","w",stdout);
	scanf("%d",&T);
	pre();
	while(T--){
		scanf("%d%lld",&n,&k);
		printf("%lld\n",f[n]);
//		ll x=find();
//		get(x);
		for(ll i=0;i<(1ll*1<<n);++i){
			printf("%lld ",count(1ll*i));
			get(i);
		}
	}
	return 0;
}

D.P4254 [JSOI2008]Blue Mary开公司

  • 别人的线段树打出来就是 \(\mathtt{\color{green}{AC}}\),我的线段树打出来只能得暴力分……

Solution

  • 李超线段树。这是一种用于维护多条直线中取某横坐标上最值的线段树。

  • 我的写法是:每个点维护当前最优直线的 \(k\)\(b\),在修改时不向下,查询时才向下;

  • 对于 \(\mathtt{change()}\):(求最大值时)

    • 当区间原来没有标记线段时,直接修改 \(k\)\(b\) 的值,返回;
    • 求出原来区间中直线的左右端点(记为 \(l_1,r_1\))和现在这条直线在区间中的左右端点(记为 \(l_2,r_2\)),判断两种特殊情况:
      • \(l_1\le l_2\)\(r_1\le r_2\),直接更换原区间标记的直线,返回;
      • \(l_2\le l_1\)\(r_2\le r_1\),不变,返回;
    • 如果不属于以上几种情况,就下分到左右子区间继续;
  • 对于 \(\mathtt{ask(x)}\):由于修改时没有下传标记,所以查询时就要把包含 \(x\) 点的所有区间中 \(x\) 的值都算一遍,取最大值即可;

  • 当然也可以在修改时下传标记,查询时直接查;

根据某同学的惨痛教训,线段树一定要记得写 change,否则你会 WA:)

AC code
#include<bits/stdc++.h>
using namespace std;

const int N=5e4+10;
const double eps=1e-8;

struct memr{
	int l,r;
//	double mx;
	double k,b;
}tr[N<<4];

int n;

double count(int x,double k,double b){
	return 1.0*x*k+b;
}

inline void build(int p,int l,int r){
	tr[p].l=l,tr[p].r=r;
	tr[p].k=tr[p].b=0;
	if(l==r) return ;
	int mid=(l+r)>>1;
	build(p<<1,l,mid);
	build(p<<1|1,mid+1,r);
	return ;
}

void change(int p,int l,int r,double k,double b){
	if(l<=tr[p].l && tr[p].r<=r){
		if(!tr[p].k && !tr[p].b){
			tr[p].k=k,tr[p].b=b;
			return ;
		}
		double l1=count(tr[p].l,tr[p].k,tr[p].b);
		double r1=count(tr[p].r,tr[p].k,tr[p].b);
		double l2=count(tr[p].l,k,b);
		double r2=count(tr[p].r,k,b);
		if(l1<=l2 && r1<=r2){
			tr[p].k=k,tr[p].b=b;
			return ;
		}
		if(l2<=l1 && r2<=r1)
			return ;
	}
	int mid=(tr[p].l+tr[p].r)>>1;
	if(l<=mid) change(p<<1,l,r,k,b);
	if(mid<r) change(p<<1|1,l,r,k,b);
	return ;
}

double ask(int p,int x,double ans){
	ans=max(ans,tr[p].k*x*1.0+tr[p].b);
	if(tr[p].l==x && tr[p].r==x)
		return ans;
	int mid=(tr[p].l+tr[p].r)>>1;
	if(x<=mid) return ask(p<<1,x,ans);
	return ask(p<<1|1,x,ans);
}

int main(){
//	freopen("company.in","r",stdin);
//	freopen("company.out","w",stdout);
	scanf("%d",&n);
	build(1,1,5e4);
	string opt;
	double x,y;
	int t;
	while(n--){
		cin>>opt;
		if(opt[0]=='Q'){
			scanf("%d",&t);
			printf("%d\n",(int)floor(1.0*ask(1,t,0)/100.0));
		}
		else{
			scanf("%lf%lf",&x,&y);
			change(1,1,5e4,y,x-y);
		}
	}
	return 0;
}
posted @ 2022-08-20 14:18  Star_LIcsAy  阅读(48)  评论(0)    收藏  举报