1、[LeetCode1631]最小体力消耗路径
二分+BFS。二分答案,用权值不大于二分值的边跑BFS,若起点终点能连通则可行。
class Solution {
public int minimumEffortPath(int[][] heights) {
int n=heights.length;
int m=heights[0].length;
int l=0,r=0;
int[][] d={{-1,0},{1,0},{0,-1},{0,1}};
for (int i=0;i<n;i++)
for (int j=0;j<m;j++)
for (int k=0;k<4;k++){
int x=i+d[k][0],y=j+d[k][1];
if (x>=0 && y>=0 && x<n && y<m)
r=Math.max(r,Math.abs(heights[i][j]-heights[x][y]));
}
int ans=r;
int[][] a=new int[10000][2];
boolean[][] b=new boolean[n][m];
for (;l<=r;){
int mid=(l+r)/2;
int h=0,t=0,x,y;
a[0][0]=a[0][1]=0;
for (int i=0;i<n;i++)
for (int j=0;j<m;j++) b[i][j]=false;
b[0][0]=true;
for (;h<=t;h++){
for (int i=0;i<4;i++){
x=a[h][0]+d[i][0];
y=a[h][1]+d[i][1];
if (x>=0 && y>=0 && x<n && y<m && !b[x][y] && Math.abs(heights[a[h][0]][a[h][1]]-heights[x][y])<=mid){
a[++t][0]=x;
a[t][1]=y;
b[x][y]=true;
if (x==n-1 && y==m-1) break;
}
}
if (b[n-1][m-1]) break;
}
if (b[n-1][m-1]){
r=mid-1;
ans=mid;
}
else l=mid+1;
}
return ans;
}
}
2、[LeetCode778]水位上升的泳池中游泳
二分+BFS。跟上题没有本质区别,同样是二分答案,再用BFS或并查集判断起点终点是否连通。要注意水位至少要比起点高。
class Solution {
public int swimInWater(int[][] grid) {
int n=grid.length;
int m=grid[0].length;
int l=grid[0][0],r=0;
int[][] d={{-1,0},{1,0},{0,-1},{0,1}};
for (int i=0;i<n;i++)
for (int j=0;j<m;j++)
r=Math.max(r,grid[i][j]);
int ans=r;
int[][] a=new int[2600][2];
boolean[][] b=new boolean[n][m];
for (;l<=r;){
int mid=(l+r)/2;
int h=0,t=0,x,y;
a[0][0]=a[0][1]=0;
for (int i=0;i<n;i++)
for (int j=0;j<m;j++) b[i][j]=false;
b[0][0]=true;
for (;h<=t;h++){
for (int i=0;i<4;i++){
x=a[h][0]+d[i][0];
y=a[h][1]+d[i][1];
if (x>=0 && y>=0 && x<n && y<m && !b[x][y] && grid[x][y]<=mid){
a[++t][0]=x;
a[t][1]=y;
b[x][y]=true;
if (x==n-1 && y==m-1) break;
}
}
if (b[n-1][m-1]) break;
}
if (b[n-1][m-1]){
r=mid-1;
ans=mid;
}
else l=mid+1;
}
return ans;
}
}
3、 [LeetCode839]相似字符串组
数据范围很友好。我直接暴力判断每两个字符串是否“相似”,若相似就放到同一个并查集里,最后看有几个集就行。(题目数据范围好像有问题,我开110爆了,直接改1000就过了)
class Solution {
int[] f=new int[1000];
public int find(int x){
if (f[x]==x) return x;
return f[x]=find(f[x]);
}
public int numSimilarGroups(String[] strs) {
int n=strs.length;
int l=strs[0].length();
for (int i=0;i<n;i++) f[i]=i;
int x,y,p1,p2,i,j,k;
boolean match;
for (i=0;i<n;i++)
for (j=i+1;j<n;j++){
x=find(i);
y=find(j);
if (x==y) continue;
for (k=0;k<l;k++)
if (strs[i].charAt(k)!=strs[j].charAt(k)) break;
if (k==l){
f[y]=x;
continue;
}
p1=k;
for (k=l-1;k>=0;k--)
if (strs[i].charAt(k)!=strs[j].charAt(k)) break;
p2=k;
if (strs[i].charAt(p1)!=strs[j].charAt(p2) || strs[i].charAt(p2)!=strs[j].charAt(p1)) continue;
match=true;
for (k=p1+1;k<p2;k++)
if (strs[i].charAt(k)!=strs[j].charAt(k)){
match=false;
break;
}
if (!match) continue;
f[y]=x;
}
int ans=0;
for (i=0;i<n;i++)
if (f[i]==i) ans++;
return ans;
}
}
4、 [NOIP2010 提高组] 关押罪犯
并查集。首先,很显然的贪心思想,一直将冲突最大的两个罪犯放在不同监狱,直到无解。对于每个罪犯,设置当前不能和他在一个监狱的罪犯的集合。显然当我们按冲突从大到小枚举所有罪犯间的关系时,若当前枚举到的罪犯x和罪犯y都在某个罪犯z的冲突集合中,就出现了无解的情况,当前枚举的冲突的答案即为题目的解。注意:判断x和y是否在一个并查集中只与是否出现无解情况有关,但有可能x已经在y的冲突集合中,反之亦然。(我的代码中用f[1~n]表示罪犯,用f[1+n~n+n]表示冲突集合)。此题还有很多方法,比如二分+dfs染色等。
#include<iostream>
#include<algorithm>
struct line{
int x,y,v;
} l[100010];
int f[40010];
int n,m;
using namespace std;
inline bool cmp(line x,line y){return x.v>y.v;}
int find(int x){
if (f[x]==x) return x;
return f[x]=find(f[x]);
}
int main(){
scanf("%d%d\n",&n,&m);
for (int i=1;i<=m;i++) scanf("%d%d%d\n",&l[i].x,&l[i].y,&l[i].v);
for (int i=1;i<=2*n;i++) f[i]=i;
sort(l+1,l+m+1,cmp);
int x,y,i;
l[m+1].v=0;
for (i=1;i<=m;i++){
x=find(l[i].x);
y=find(l[i].y);
if (x==y) break;
if (x!=find(l[i].y+n)) f[x]=f[l[i].y+n];
if (y!=find(l[i].x+n)) f[y]=f[l[i].x+n];
}
printf("%d",l[i].v);
}
5、[NOIP2017 提高组] 奶酪
并查集。题目比较简单,BFS也行,注意数据范围即可。
#include<iostream>
#include<math.h>
int x[1010],y[1010],z[1010],f[1010];
using namespace std;
int find(int x){
if (f[x]==x) return x;
return f[x]=find(f[x]);
}
inline long double dis(int i,int j){
return sqrt(((long double)x[i]-x[j])*((long double)x[i]-x[j])+((long double)y[i]-y[j])*((long double)y[i]-y[j])+((long double)z[i]-z[j])*((long double)z[i]-z[j]));
}
int main(){
int num,n,h,r;
scanf("%d",&num);
for (;num;num--){
scanf("%d%d%d\n",&n,&h,&r);
int d=2*r;
z[n+1]=2e9;
for (int i=0;i<=n+1;i++) f[i]=i;
for (int i=1;i<=n;i++){
scanf("%d%d%d\n",&x[i],&y[i],&z[i]);
if (z[i]<=r){
if (z[find(0)]<=z[i]) f[find(0)]=i;
else f[i]=find(0);
}
if (z[i]>=h-r) f[i]=n+1;
}
int p,q;
for (int i=1;i<n && find(0)!=n+1;i++)
for (int j=i+1;j<=n;j++){
if (dis(i,j)>d) continue;
p=find(i);
q=find(j);
if (p==q) continue;
if (z[p]>z[q]) f[q]=p;
else f[p]=q;
}
if (find(0)==n+1) printf("Yes\n");
else printf("No\n");
}
}
6、[LeetCode424]替换后的最长重复字符
思想题。枚举最长重复字符串的字母,枚举左端点,每个右端点可以由之前的右端点推得。
#include<string>
#include<iostream>
using namespace std;
class Solution {
public:
int characterReplacement(string s, int k) {
int n=s.length(),ans=0;
for (int i=0;i<26;i++){
int l=0,r=-1,t=0;
for (;r<n-1 && s[r+1]-'A'==i;r++);
for (;t<k && r<n-1;t++){
for (r++;s[r]-'A'==i && r<n-1;r++);
for (;r<n-1 && s[r+1]-'A'==i;r++);
}
ans=max(ans,r+1);
while (r<n-1){
for (;s[l]-'A'==i && l<n-1;l++);
l++;
for (r++;r<n-1 && s[r+1]-'A'==i;r++);
ans=max(ans,r-l+1);
}
}
return ans;
}
};
7、[USACO08FEB]Hotel G
线段树。维护每个区间的tmx,lmx,rmx,分别表示这个区间最左(右)边最多连续空房间数,和这个区间任意位置最多连续空房间数。
#include<iostream>
#define lson t<<1
#define rson (t<<1)+1
int n,m,tmx[200001],lmx[200001],rmx[200001],len[200001],tag[200001];
using namespace std;
void build(int t,int l,int r){
lmx[t]=rmx[t]=tmx[t]=len[t]=r-l+1;
if (l==r) return;
int mid=(l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
}
void mark(int t,int l,int r,int type){
tag[t]=type;
if (type==1) lmx[t]=rmx[t]=tmx[t]=0;
else lmx[t]=rmx[t]=tmx[t]=len[t];
}
void pushdown(int t,int ls,int rs,int l,int r){
if (tag[t]==1){
lmx[ls]=rmx[ls]=tmx[ls]=0;
lmx[rs]=rmx[rs]=tmx[rs]=0;
}
else{
lmx[ls]=rmx[ls]=tmx[ls]=len[ls];
lmx[rs]=rmx[rs]=tmx[rs]=len[rs];
}
tag[ls]=tag[rs]=tag[t];
tag[t]=0;
}
void pushup(int t,int ls,int rs,int l,int r){
lmx[t]=lmx[ls];
if (lmx[t]==len[ls]) lmx[t]+=lmx[rs];
rmx[t]=rmx[rs];
if (rmx[t]==len[rs]) rmx[t]+=rmx[ls];
tmx[t]=max(tmx[ls],tmx[rs]);
tmx[t]=max(tmx[t],rmx[ls]+lmx[rs]);
}
int query(int t,int l,int r,int x){
if (l==r) return l;
int mid=(l+r)>>1;
if (tag[t] && l!=r) pushdown(t,lson,rson,l,r);
if (tmx[lson]>=x) return query(lson,l,mid,x);
if (rmx[lson]+lmx[rson]>=x) return mid-rmx[lson]+1;
return query(rson,mid+1,r,x);
}
void update(int t,int l,int r,int x,int y,int type){
if (x<=l && r<=y){
mark(t,l,r,type);
return;
}
if (tag[t] && l!=r) pushdown(t,lson,rson,l,r);
int mid=(l+r)>>1;
if (x<=mid) update(lson,l,mid,x,y,type);
if (y>mid) update(rson,mid+1,r,x,y,type);
pushup(t,lson,rson,l,r);
}
int main(){
scanf("%d%d\n",&n,&m);
build(1,1,n);
int x,y,type;
for (;m;m--){
scanf("%d",&type);
if (type==1){
scanf("%d\n",&x);
if (tmx[1]<x) printf("0\n");
else{
int ans=query(1,1,n,x);
printf("%d\n",ans);
update(1,1,n,ans,ans+x-1,1);
}
}
else{
scanf("%d%d\n",&x,&y);
update(1,1,n,x,x+y-1,2);
}
}
}
8、[USACO07JAN]Balanced Lineup G
ST表/线段树/树状数组。因为是离线所以我选择用ST表实现。a[i][j]表示从i到i+2^j-1中的最大值,b[i][j]则为最小值。
#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int n,m,a[50001][16],b[50001][16];
int query(int l,int r){
int k=log2(r-l+1);
return max(a[l][k],a[r-(1<<k)+1][k])-min(b[l][k],b[r-(1<<k)+1][k]);
}
int main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%d%d\n",&n,&m);
for (int i=1;i<=n;i++){
scanf("%d\n",&a[i][0]);
b[i][0]=a[i][0];
}
for (int j=1;(1<<j)<=n;j++)
for (int i=1;i+(1<<j)-1<=n;i++){
a[i][j]=max(a[i][j-1],a[i+(1<<(j-1))][j-1]);
b[i][j]=min(b[i][j-1],b[i+(1<<(j-1))][j-1]);
}
for (;m;m--){
int x,y;
scanf("%d%d\n",&x,&y);
printf("%d\n",query(x,y));
}
fclose(stdin);
fclose(stdout);
}
9、[POJ2352]Stars
树状数组。因为题目是按纵坐标从小到大给点,所以纵坐标没啥用,按横坐标维护一个树状数组即可。
#include<iostream>
using namespace std;
int n,x,y,a[32010],ans[15010];
int sum(int x){
int t=0;
for (;x;x-=x&(-x)) t+=a[x];
return t;
}
void add(int x){
for(;x<=32001;x+=x&(-x)) a[x]++;
}
int main(){
scanf("%d\n",&n);
for (int i=0;i<n;i++){
scanf("%d%d\n",&x,&y);
ans[sum(x+1)]++;
add(x+1);
}
for (int i=0;i<n;i++) printf("%d\n",ans[i]);
}
10、[CQOI2010]内部白点
树状数组。显然不会出现输出-1的情况。不难证明其实只有第一秒会出现新的黑点,所以这道题就转变成了:将同一行相邻点连成横线,将同一列相邻点连成竖线,求有多少个交点的问题。我们可以设想一条水平扫描线从下往上扫描,用树状数组实时维护与扫描线相交的竖线的横坐标信息,当扫描线遇到横线时求和累积到ans中。
#include<iostream>
#include<algorithm>
struct point{
int x,y;
} a[100001];
struct line{
int t,x1,x2,y;
} l[400001];
int n,h[100001],f[100001];
using namespace std;
inline bool cmp1(point a,point b){
if (a.y==b.y) return a.x<b.x;
return a.y<b.y;
}
inline bool cmp2(point a,point b){
if (a.x==b.x) return a.y<b.y;
return a.x<b.x;
}
inline bool cmp3(line a,line b){
if (a.y==b.y) return a.t<b.t;
return a.y<b.y;
}
int Hash(int x){
for (int l=1,r=n,mid;l<=r;){
mid=(l+r)>>1;
if (h[mid]==x) return mid;
if (h[mid]>x) r=mid-1;
else l=mid+1;
}
}
int sum(int x){
int s=0;
for (;x;x-=x&(-x)) s+=f[x];
return s;
}
void update(int x,int t){
for (;x<=n;x+=x&(-x)) f[x]+=t;
}
int main(){
scanf("%d\n",&n);
for (int i=1;i<=n;i++){
scanf("%d%d\n",&a[i].x,&a[i].y);
h[i]=a[i].x;
}
sort(h+1,h+n+1);
int m=0;
sort(a+1,a+n+1,cmp1);
for (int i=1;i<=n;i++)
if (a[i].y==a[i-1].y)
l[++m].t=0,l[m].x1=Hash(a[i-1].x),l[m].x2=Hash(a[i].x),l[m].y=a[i].y;
sort(a+1,a+n+1,cmp2);
for (int i=1;i<=n;i++)
if (a[i].x==a[i-1].x){
l[++m].t=1,l[m].x1=Hash(a[i].x),l[m].y=a[i-1].y;
l[++m].t=2,l[m].x1=l[m-1].x1,l[m].y=a[i].y;
}
sort(l+1,l+m+1,cmp3);
int ans=0;
for (int i=1;i<=m;i++)
if (!l[i].t) ans+=sum(l[i].x2-1)-sum(l[i].x1);
else if (l[i].t==1) update(l[i].x1,1);
else update(l[i].x1,-1);
printf("%d",ans+n);
}
11、[NOIP2012 提高组] 借教室
线段树 / 二分。听说线段树会被卡常数,鉴于我的呆马向来都比较丑就没有尝试了。正解是二分+差分的思想,二分找不能实现的第一笔订单,然后维护一个前缀和数组b,对于一笔订单num,l,r,只要使b[l]+num,b[r+1]-num,在求前缀和后就能使b[l~r]都+num,这样就能用O(M+N)的时间复杂度完成判断。
#include<iostream>
#include<cstring>
int n,m,ans,a[1000010],b[1000010],num[1000010],l[1000010],r[1000010];
using namespace std;
bool check(int p){
memset(b,0,sizeof(b));
for (;p;p--) b[l[p]]+=num[p],b[r[p]+1]-=num[p];
for (int i=1;i<=n;i++){
b[i]+=b[i-1];
if (b[i]>a[i]) return false;
}
return true;
}
int main(){
scanf("%d%d\n",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",&a[i]);
for (int i=1;i<=m;i++) scanf("%d%d%d\n",&num[i],&l[i],&r[i]);
if (check(m)){
printf("0");
return 0;
}
printf("-1\n");
for (int l=1,r=m,mid;l<=r;){
mid=(l+r)>>1;
if (check(mid)) l=mid+1;
else ans=mid,r=mid-1;
}
printf("%d",ans);
}
12、[USACO04NOV]Apple Catching G
DP。f[i][j]表示i时刻已经移动j次能拿到的最多苹果数。
#include<iostream>
using namespace std;
int n,m,p,ans,f[1001][31];
int main(){
scanf("%d%d\n",&n,&m);
for (int i=1;i<=n;i++){
scanf("%d\n",&p);
f[i][0]=f[i-1][0];
if (p==1) f[i][0]++;
for (int j=1;j<=i && j<=m;j++){
f[i][j]=max(f[i-1][j],f[i-1][j-1]);
if ((j&1)+1==p) f[i][j]++;
}
}
int ans=0;
for (int j=0;j<=m;j++) ans=max(ans,f[n][j]);
printf("%d",ans);
}
13、 [USACO09OPEN]Ski Lessons G
DP。先预处理出两个数组a和b。a[i][j]表示i时刻恰好结束的能将滑雪能力提升到j的课程的开始时间。b[i]表示滑雪能力为i时滑一次雪所需的最短时间。f[i][j]表示i时刻滑雪能力为j的最多滑雪次数。g[i]=max{f[i][1]...f[i][s]}。状态转移方程在第22-24行,应该不难理解。
#include<iostream>
#include<cstring>
int t,m,n,s,x,y,z,a[10010][110],b[110],f[10010][110],g[10010];
using namespace std;
int main(){
scanf("%d%d%d\n",&t,&m,&n);
s=1;
for (;m;m--){
scanf("%d%d%d\n",&x,&y,&z);
a[x+y][z]=max(a[x+y][z],x);
s=max(s,z);
}
memset(b,0x3f,sizeof(b));
for (;n;n--){
scanf("%d%d\n",&x,&y);
for (;x<=s;x++) b[x]=min(b[x],y);
}
memset(f,0xcf,sizeof(f));
f[0][1]=0;
for (int i=1;i<=t;i++)
for (int j=1;j<=s;j++){
f[i][j]=f[i-1][j];
if (a[i][j]) f[i][j]=max(f[i][j],g[a[i][j]]);
if (i>=b[j]) f[i][j]=max(f[i][j],f[i-b[j]][j]+1);
g[i]=max(g[i],f[i][j]);
}
printf("%d",g[t]);
}
14、[USACO09DEC]Bobsledding S
说是DP,更像贪心。先从后往前预处理一下速度限制,将速度上限直接控制在就算极限刹车也一定能顺利通过后面所有转弯的数值。然后从前往后推就行了。算答案时用第15行的式子,基本思想就是一直加速到必须减速,这里不再详细推导。
#include<iostream>
#include<algorithm>
struct p{int t,s;} a[100010];
int l,n,ans,f[100010];
using namespace std;
bool cmp(p x,p y){return x.t<y.t;}
int main(){
scanf("%d%d\n",&l,&n);
for (int i=1;i<=n;i++) scanf("%d%d\n",&a[i].t,&a[i].s);
sort(a+1,a+n+1,cmp);
for (int i=n-1;i;i--) a[i].s=min(a[i].s,a[i+1].s+a[i+1].t-a[i].t);
f[0]=1;
for (int i=1;i<=n;i++){
f[i]=min(a[i].s,f[i-1]+a[i].t-a[i-1].t);
ans=max(ans,(f[i]+f[i-1]+a[i].t-a[i-1].t)>>1);
}
printf("%d",max(ans,f[n]+l-a[n].t));
}
15、[USACO09MAR]Cow Frisbee Team S
DP。f[t][i]表示余数为i的方案数,t只有0和1,用作滚动数组。看数据范围不用滚动数组应该也行。最终答案记得减一。
#include<iostream>
const int mo=100000000;
int n,m,x,t,f[2][1001];
using namespace std;
int main(){
scanf("%d%d\n",&n,&m);
f[0][0]=f[1][0]=1;
for (;n;n--){
scanf("%d\n",&x);
x%=m;
t=n&1;
for (int i=x;i<m;i++) f[t][i]=(f[1-t][i]+f[1-t][i-x])%mo;
for (int i=0;i<x;i++) f[t][i]=(f[1-t][i]+f[1-t][m+i-x])%mo;
}
printf("%d",f[1][0]-1);
}