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;
}

浙公网安备 33010602011771号