题目链接
D
题目大意
给两个数组,一个长度为
,一个长度为
,其中
。
现从中任意取出元素构成
长度为
的数组
,问
最大能为多少。
解题思路
- 考虑每次从
中选一个,再从
中选一个进行配对
- 对
从小到大排序,这样先取序列两头进行处理更优。因为绝对值的差值,显然极值相差最大
- 对于每次
选头还是尾,相应
选头还是尾,都有可能,所以采用动态规划
定义分别表示第i次选
的头(0)或尾(1)后,得到最大值时,新的
序列,
序列和最大值。
同理
可以先用进行更新,若
得到的结果小,则不用更新,直接
没有很复杂吧,代码重复性挺高的,很好写
import java.io.*;
import java.util.Arrays;
import java.util.Scanner;
public class Main{
public static void main(String[] args) throws IOException{
Scanner input=new Scanner(System.in);
int T=input.nextInt();
while(true) {
if(T==0)break;
int n=input.nextInt();
int m=input.nextInt();
long[] a=new long[n];
long[] b=new long[m];
for(int i=0;i<n;++i)a[i]=input.nextLong();
for(int i=0;i<m;++i)b[i]=input.nextLong();
Arrays.sort(a);
Arrays.sort(b);
Node[][] f=new Node[n+1][2];
//从0开始,单独做一次
int al=0;
int ar=n-1;
int bl=0;
int br=m-1;
long fll=Math.abs(a[al]-b[bl]);
long flr=Math.abs(a[al]-b[br]);
long frl=Math.abs(a[ar]-b[bl]);
long frr=Math.abs(a[ar]-b[br]);
if(fll<=flr) {
al++;br--;
f[0][0]=new Node(al,ar,bl,br);
f[0][0].ans=flr;
al--;br++;//记得补回去,下面还要用
}else {
al++;bl++;
f[0][0]=new Node(al,ar,bl,br);
f[0][0].ans=fll;
al--;bl--;
}
if(frl<=frr) {
ar--;br--;
f[0][1]=new Node(al,ar,bl,br);
f[0][1].ans=frr;
ar++;br++;
}else {
ar--;bl++;
f[0][1]=new Node(al,ar,bl,br);
f[0][1].ans=frl;
ar++;bl--;
}
for(int i=1;i<n;++i) {
al=f[i-1][0].al;
ar=f[i-1][0].ar;
bl=f[i-1][0].bl;
br=f[i-1][0].br;
fll=Math.abs(a[al]-b[bl]);
flr=Math.abs(a[al]-b[br]);
frl=Math.abs(a[ar]-b[bl]);
frr=Math.abs(a[ar]-b[br]);
if(fll<=flr) {
al++;br--;
f[i][0]=new Node(al,ar,bl,br);
f[i][0].ans=f[i-1][0].ans+flr;
al--;br++;
}else {
al++;bl++;
f[i][0]=new Node(al,ar,bl,br);
f[i][0].ans=f[i-1][0].ans+fll;
al--;bl--;
}
if(frl<=frr) {
ar--;br--;
f[i][1]=new Node(al,ar,bl,br);
f[i][1].ans=f[i-1][0].ans+frr;
ar++;br++;
}else {
ar--;bl++;
f[i][1]=new Node(al,ar,bl,br);
f[i][1].ans=f[i-1][0].ans+frl;
ar++;bl--;
}
al=f[i-1][1].al;
ar=f[i-1][1].ar;
bl=f[i-1][1].bl;
br=f[i-1][1].br;
fll=Math.abs(a[al]-b[bl]);
flr=Math.abs(a[al]-b[br]);
frl=Math.abs(a[ar]-b[bl]);
frr=Math.abs(a[ar]-b[br]);
if(fll<=flr) {
long ft=f[i-1][1].ans+flr;
if(ft>f[i][0].ans) {
al++;br--;
f[i][0]=new Node(al,ar,bl,br);
f[i][0].ans=f[i-1][1].ans+flr;
al--;br++;
}
}else {
long ft=f[i-1][1].ans+fll;
if(ft>f[i][0].ans) {
al++;bl++;
f[i][0]=new Node(al,ar,bl,br);
f[i][0].ans=f[i-1][1].ans+fll;
al--;bl--;
}
}
if(frl<=frr) {
long ft=f[i-1][1].ans+frr;
if(ft>f[i][1].ans) {
ar--;br--;
f[i][1]=new Node(al,ar,bl,br);
f[i][1].ans=f[i-1][1].ans+frr;
ar++;br++;
}
}else {
long ft=f[i-1][1].ans+frl;
if(ft>f[i][1].ans) {
ar--;bl++;
f[i][1]=new Node(al,ar,bl,br);
f[i][1].ans=f[i-1][1].ans+frl;
ar++;bl--;
}
}
}
System.out.println(Math.max(f[n-1][0].ans,f[n-1][1].ans));
T--;
}
}
}
class Node{
int al;
int ar;
int bl;
int br;
long ans;
public Node(int aL,int aR,int bL,int bR) {
al=aL;
ar=aR;
bl=bL;
br=bR;
}
}
E
题目大意
在一个棋盘内,有两个不同的点,
只能往左下,下,右下走,
只能往左上,上,右上走。
先走,
后走,问最终是
吃
,还是
吃
,或者不相遇为平局。双方都不傻。
解题思路
- 若初始
在
的下面或在同一行,则不可能相遇,为平局
- 若
和
之间行差为奇数,则最后一步为
走出,所以若相遇则
赢,否则
跑
追
- 若
和
之间行差为偶数,则最后一步为
走出,所以若相遇则
赢,否则
跑
追
- 由于
先手,当差为奇数时判断第一次
能不能直接吃,不给
跑的机会
- 当差为偶数,且
在同一列,则
无论怎么跑都会被
吃
- 当跑的人跑到边界,则不能跑了,只能乖乖等死。若追的人,在总步数内到不了边界,则平局
import java.io.*;
import java.util.Scanner;
public class Main{
public static void main(String[] args) throws IOException{
Scanner input=new Scanner(System.in);
int T=input.nextInt();
while(true) {
if(T==0) break;
int h=input.nextInt();
int w=input.nextInt();
int a=input.nextInt();
int b=input.nextInt();
int c=input.nextInt();
int d=input.nextInt();
//A在B下面或同一行
if(a>=c) {
System.out.println("Draw");
T--;
continue;
}
int x=c-a;
if(x%2!=0) {
//则A/D
//A上来直接吃
if(Math.abs(b-d)<=1) {
System.out.println("Alice");
T--;
continue;
}
//Alice跑到边界追上Bob的步数
int may=b<d?w-b:b-1;
//总步数
int step=x/2+1;
if(may<=step) {
System.out.println("Alice");
T--;
continue;
}else {
System.out.println("Draw");
T--;
continue;
}
}else {
//则B/D
//Alice直接开跑
//但是若在一列Bob直接跟,不到墙也能吃
if(b==d) {
System.out.println("Bob");
T--;
continue;
}
//Bob到边界追上Alice的步数
int may=b<d?d-1:w-d;
//总步数
int step=x/2;
if(may<=step) {
System.out.println("Bob");
T--;
continue;
}else {
System.out.println("Draw");
T--;
continue;
}
}
}
}
}
F
题目大意
给一个长度为的数组,有
次询问。查询
解题思路
- 直接暴力
,当
很小时,
取值范围会变得很大,当取最大时,可以达到
,复杂度很高
- 发现当依次进行取数时,取数的下标只跟
有关,即
。且
的大小会影响
可能的取值,进而影响复杂度。而当
时,直接暴力的复杂度最大变为
,是可以接受的。所以考虑按照
的大小将问题拆为两个子问题,也就是典型的根号分治。
- 对于
,知道相应序列下标,考虑前缀和优化。预处理
表示距离为
的元素所构成的序列,结尾为
的前缀和。由于每个元素还要乘上相应系数
,所以预处理
- 数据很大,要用快输
例:
已知,要查询
的序列,则
import java.io.*;
import java.util.Scanner;
import java.util.StringTokenizer;
public class Main{
public static void main(String[] args) throws IOException{
AReader input=new AReader();
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
int T=input.nextInt();
while(true) {
if(T==0) break;
int n=input.nextInt();
int q=input.nextInt();
int w=(int)Math.sqrt(n);
long[] a=new long[n+1];
long[][] f=new long[w+1][n+1];
long[][] g=new long[w+1][n+1];
for(int i=1;i<=n;++i)a[i]=input.nextLong();
//预处理
for(int i=1;i<=w;++i) {
for(int t=0;t<i;++t) {
f[i][t]=a[t];
g[i][t]=a[t];
for(int j=t+i;j<=n;j+=i) {
f[i][j]=f[i][j-i]+a[j]*(j/i+1);//j/i是下取整
g[i][j]=g[i][j-i]+a[j];
}
}
}
while(true) {
if(q==0) break;
int s=input.nextInt();
int d=input.nextInt();
int k=input.nextInt();
long ans=0;
if(d>w) {//暴力
int t=s;
for(int i=1;i<=k;++i) {
ans+=a[t]*i;
t=t+d;
}
}else {
if(d>s) {//s-d<0
ans=f[d][s+d*(k-1)];
}else {
ans=(f[d][s+d*(k-1)]-f[d][s-d])-(g[d][s+d*(k-1)]-g[d][s-d])*(s/d);
}
}
out.print(ans+" ");
q--;
}
out.println();
T--;
}
out.flush();
out.close();
}
static
class AReader {
private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
private StringTokenizer tokenizer = new StringTokenizer("");
private String innerNextLine() {
try {
return reader.readLine();
} catch (IOException ex) {
return null;
}
}
public boolean hasNext() {
while (!tokenizer.hasMoreTokens()) {
String nextLine = innerNextLine();
if (nextLine == null) {
return false;
}
tokenizer = new StringTokenizer(nextLine);
}
return true;
}
public String nextLine() {
tokenizer = new StringTokenizer("");
return innerNextLine();
}
public String next() {
hasNext();
return tokenizer.nextToken();
}
public int nextInt() {
return Integer.parseInt(next());
}
public long nextLong() {
return Long.parseLong(next());
}
}
}
G
题目大意
给一个图,图上有一些特殊点。在图上任选一点,从这个点开始向左上,右上,左下,右下,用边长为的三角形进行覆盖,问能覆盖到的最大特殊点数。
解题思路
- 注意题中所给出的坐标系与平常的不一样
- 改变三角形,不如旋转图本身,便于考虑
- 图旋转后的下标对应关系,自己可以在草稿纸上推出
- 这里给出顺时针旋转方法:
- 从4种三角形种任选一种在图上进行扫描,对于旋转过后的4种图进行4次相同操作
- 这里选择向左下的三角形
- 每次移动,会新增加移动后竖边那一列的特殊点,新减少移动之前三角形的斜线上的特殊点
- 对于增加列,采用二维前缀和进行计算
- 对于减少斜线,先预处理出斜线前缀和,然后对于每次移动时利用等腰直角三角形(在纸上多推导),求出移动前完整三角形左上的点坐标,和右下的点坐标,再进行计算。
import java.io.*;
import java.util.Scanner;
import java.util.StringTokenizer;
public class Main{
public static void main(String[] args) throws IOException{
AReader input=new AReader();
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
int T=input.nextInt();
while(true) {
if(T==0) break;
int n=input.nextInt();
int m=input.nextInt();
int k=input.nextInt();
char[][] map=new char[n+1][m+1];//n*m<=1e5
for(int i=1;i<=n;++i) {
String s=" "+input.next();
map[i]=s.toCharArray();
}
int ans=0;
for(int turn=1;turn<=4;++turn) {
int[][] sum=new int[n+1][m+1];
int[][] b=new int[n+1][m+1];//斜线
//前缀和
for(int i=1;i<=n;++i) {
for(int j=1;j<=m;++j) {
if(map[i][j]=='#') {
sum[i][j]++;
b[i][j]++;
}
sum[i][j]+=sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1];
b[i][j]+=b[i-1][j-1];
}
}
for(int i=1;i<=n;++i) {
int now=0;
for(int j=1;j<=m;++j) {
int di=Math.min(n,i+k);//右下坐标的i
//加一列
now+=sum[di][j]-sum[i-1][j]-sum[di][j-1]+sum[i-1][j-1];
//处理斜线
if(j-1<1) {//移动之前三角形还没有覆盖
ans=Math.max(ans, now);
continue;
}
int zuoshangj=Math.max(1, j-k-1);
//超出边界时,取边界(或边界的延长线)与三角形的交点为左上
int zuoshangi=zuoshangj-(j-1-k)+i;
//左上坐标的i
int youxiaj=j-1-Math.max(i+k-n, 0);
//右下坐标的j
if(youxiaj<1||zuoshangi>n) {
//边界与三角形的交点在图像范围之外,即斜线与图没有重叠
ans=Math.max(ans, now);
continue;
}
now-=b[di][youxiaj]-b[zuoshangi-1][zuoshangj-1];
ans=Math.max(ans, now);
}
}
int t=n;
n=m;
m=t;
//顺时针旋转
char[][] map2=new char[n+1][m+1];
for(int i=1;i<=n;++i) {
for(int j=1;j<=m;++j) {
map2[i][j]=map[m-j+1][i];
}
}
map=map2;
}
System.out.println(ans);
T--;
}
out.flush();
out.close();
}
static
class AReader {
private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
private StringTokenizer tokenizer = new StringTokenizer("");
private String innerNextLine() {
try {
return reader.readLine();
} catch (IOException ex) {
return null;
}
}
public boolean hasNext() {
while (!tokenizer.hasMoreTokens()) {
String nextLine = innerNextLine();
if (nextLine == null) {
return false;
}
tokenizer = new StringTokenizer(nextLine);
}
return true;
}
public String nextLine() {
tokenizer = new StringTokenizer("");
return innerNextLine();
}
public String next() {
hasNext();
return tokenizer.nextToken();
}
public int nextInt() {
return Integer.parseInt(next());
}
public long nextLong() {
return Long.parseLong(next());
}
}
}