トピックPDFダウンロード:第14回蘭橋杯地方大会のPDFダウンロード
目次
質問 A: 特別な日
質問の意味:指定された期間内で、年数が月数と日数の倍数であることを調べます。つまり、年 % 月 == 0 && 年 % 日 = = 0
アイデア: シミュレーション、私の答えは: 35813063
日数を数えるために使うもので、ブルーブリッジカップではこれがよく出てきます。
コード:
import java.util.*;
public class Main {
static int pre[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
public static void main(String[] args) {
int sum=0;
//i是年,j是月,k是日
for(int i=2000;i<=2000000;i++){
for(int j=1;j<=12;j++){
int r=pre[j];
if(j==2 && (i%400==0 || (i%100!=0 && i%4==0))) r++;
//2月的闰年是29天
for(int k=1;k<=r;k++){
if(i%j==0 && i%k==0) sum++;
if(i==2000000) { //2000000年1月1号过后,直接跳出
i=2000001;
j=13;
break;
}
}
}
}
System.out.println(sum);
}
}
質問 B: AND OR XOR
質問の意味:上から下に 5 つの数値を計算し、隣り合う 2 つの数値をペアとして計算します。演算子は &、|、または ^ です。これら 5 つの数字がわかれば、演算子は何個見つかるか、最終的な答えは 1 になります。
アイデア: dfs 検索、私の答え: 30528
dfs は、すべての演算子の配置 (pow(3,10)=59049) を列挙し、最終結果が 1 つの答えの数値 + 1 である場合、これら 5 つの数値を上から下に計算します。
コード (C++ で書かれた):
#include <bits/stdc++.h>
using namespace std;
int sum=0;
int a[100005];
int dp[10][10];
int mp[10][10];
void dfs(int d){
if(d==11){
for(int i=1;i<=4;i++) mp[1][i]=a[i];
for(int i=1;i<=3;i++) mp[2][i]=a[i+4];
for(int i=1;i<=2;i++) mp[3][i]=a[i+7];
for(int i=1;i<=1;i++) mp[4][i]=a[10];
for(int i=1;i<=4;i++){
for(int j=1;j<=4-i+1;j++){
if(mp[i][j]==0) dp[i][j]=dp[i-1][j]|dp[i-1][j+1];
if(mp[i][j]==1) dp[i][j]=dp[i-1][j]^dp[i-1][j+1];
if(mp[i][j]==2) dp[i][j]=dp[i-1][j]&dp[i-1][j+1];
}
}
if(dp[4][1]==1)
sum++;
return;
}
a[d]=0;
dfs(d+1);
a[d]=1;
dfs(d+1);
a[d]=2;
dfs(d+1);
}
int main()
{
dp[0][1]=1;
dp[0][2]=0;
dp[0][3]=1;
dp[0][4]=0;
dp[0][5]=1;
dfs(1);
cout<<sum<<endl;
return 0;
}
質問 C: 平均的
質問の意味:数値 (0 ~ 9 の範囲) とその変更コストが与えられた場合、最終的な数値 0 ~ 9 が同じになる最小コストを見つけてください。
アイデア: 貪欲
>n/10 の数値を他の数値に変更するだけです。つまり、>n/10 の数値を変更する必要があります (数量 -n/10)。もちろん、小さな変化も見つけてください
パス: アイデアの時間計算量は 100% になる可能性があります。具体的なパスは状況によって異なります。
コード:
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner cin =new Scanner(System.in);
int n=cin.nextInt();
int A[][]=new int[11][100005];
int len[]=new int[15];
for(int i=1;i<=n;i++) {
int x=cin.nextInt();
int y=cin.nextInt();
A[x][++len[x]]=y;
}
for(int i=0;i<10;i++)
Arrays.sort(A[i],1,len[i]+1); //排序,不用list是因为没有板子,手敲不出来
long sum=0;
for(int i=0;i<10;i++) {
for(int j=1;j<=len[i]-n/10;j++) {
sum+=A[i][j];
}
}
System.out.println(sum);
}
}
質問 D: チェス盤
質問の意味: n*m 個のチェス盤は、最初は白い駒でいっぱいです。長方形を選択してすべてを反転し、最終的なチェス盤の状況を出力します。
アイデア: 差分プレフィックス合計
この四角形のすべての数値に 1 を加算し (最初はすべて 0 でした)、最終的に %2 が答えになります。
最大データは 2000 件のみであるため、変更される行のたびに差分が変更されます。合計実行数はわずか 2000*2000 です。
最後に、接頭語と行ごとにこれらの数字 %2 を印刷します。印刷時にスペースが入らないように注意してください。
パス: アイデアの時間計算量は 100% になる可能性があります。具体的なパスは状況によって異なります。
コード:
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner cin =new Scanner(System.in);
int n=cin.nextInt();
int m=cin.nextInt();
int A[][]=new int[2005][2005];
for(int i=1;i<=m;i++) {
int x1=cin.nextInt();
int y1=cin.nextInt();
int x2=cin.nextInt();
int y2=cin.nextInt();
for(int j=x1;j<=x2;j++) {
A[j][y1]++; //差分
A[j][y2+1]--;
}
}
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
A[i][j]+=A[i][j-1]; //前缀和
System.out.print(A[i][j]%2);
}
System.out.println();
}
}
}
質問 E: 相互素数の数
質問の意味: pow(a,b) と比較的素な 1-pow(a,b) にはいくつの数値がありますか
アイデア: 思考 + GCD + 高速パワー
答えは pow(a,b-1)*r です。r は 1-a において a と互いに素である量です。
そして a*a*a*a.... は互いに素、つまり a と互いに素な数です。
a と b の相互素数は gcd(a, b)==1 です。そして (a, b) と (a, b+a)、および (a, b+a*2)... は同じです。gcd() の式で確認できるはずです。
つまり、サイクルがあります。1-a を見てください。そして r はオイラー関数です
合格:
オイラー関数は 100% になる可能性があります
100% コード:
import java.util.*;
public class Main{
static long mod=998244353;
static long Euler(long n){ //求欧拉函数值
long res=n;
for(long i=2;i*i<=n;++i){
if(n%i==0){
res=res/i*(i-1);
while(n%i==0)
n/=i;
}
}
if(n>1)
res-=res/n;
return res;
}
static long qpow(long a,long b){
long ans=1;
while(b!=0){
if(b%2==1)
ans=ans*a%mod;
a=a*a%mod;
b/=2;
}
return ans;
}
public static void main(String[] args){
Scanner cin =new Scanner(System.in);
long a=cin.nextLong();
long b=cin.nextLong();
long r=Euler(a); //1-a中有多少个数和a互质
System.out.println(qpow(a,b-1)*(r%mod)%mod);
}
}
質問 F: 階乗の合計
タイトルの意味:
アイデア: この質問にとってこれ以上のアイデアはありません。
最大の m を乱暴に計算する場合は、乗算剰余式のみを使用してください。
ans=1,2,6,24,120.... これらの階乗の合計が ans で割り切れるかどうかを計算します。つまり、次のように判断します。
A[1]!%ans+A[2]!%ans+....+A[n]!%ans==0
そうでない場合は、現在の ans に対応する階乗数を出力します。
合格:
この方法では上位 40% を通過できない可能性があることがわかりますが、ほとんどの場合、一部を通過することは可能です。
コード:
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner cin =new Scanner(System.in);
int n=cin.nextInt();
int a[]=new int[n+10];
for(int i=1;i<=n;i++) {
a[i]=cin.nextInt();
}
long ans=1,p=1;
while(true) {
long sum=0;
for(int i=1;i<=n;i++) {
long res=1;
for(int j=2;j<=a[i];j++) {
res=res*j%ans;
}
sum=(sum+res)%ans; //算和
}
if(sum==0) {
ans*=(++p);
}else {
break;
}
}
System.out.println(p-1);
}
}
質問 G: シャオランの旅行計画
アイデア: 思考 + 優先キュー
以前に購入できるすべての単価と数量を記録し、石油がなくなったら最小価格を削除します。
100% のアイデアは、長さ 2 のリストを保存し、毎回最低価格をポップアップ表示し、その数量を変更する優先キューである必要があります。0になると押されなくなります。
コメントによれば、燃料タンクにはmの上限があるため、この考えは完全に正しいとは言えず、完全に正しい考えは思いつきません。
合格:
プライオリティ キューは、長さ 2 のリストを 100% 格納できます。
コード (優先キューの 60% が数量に応じて格納されます):
import java.util.*;
public class Main{
public static void main(String[] args){
Scanner cin =new Scanner(System.in);
PriorityQueue q=new PriorityQueue();
int n=cin.nextInt();
int m=cin.nextInt();
long sum=0;
for(int i=1;i<=n;i++) {
int x=cin.nextInt();
int y=cin.nextInt();
int z=cin.nextInt();
m-=x;
while(m<0) {
if(q.isEmpty()) {
sum=-1;
i=n+1;
break;
}
int r=(int)q.poll();
sum+=r;
m++;
}
for(int j=1;j<=z;j++) {
q.add(y);
}
}
System.out.println(sum);
}
}
コード (優先キューは 100% 順番に格納されます):
import java.lang.reflect.Array;
import java.util.*;
public class Main{
static ArrayList fun(int x,int y){
ArrayList<Integer> t=new ArrayList();
t.add(x);
t.add(y);
return t;
}
public static void main(String[] args){
Scanner cin =new Scanner(System.in);
PriorityQueue<ArrayList<Integer>> q=new PriorityQueue<>(new Comparator<ArrayList<Integer>>(){
@Override
public int compare(ArrayList<Integer> a, ArrayList<Integer> b){
return a.get(0)-b.get(0);
}
});
int n=cin.nextInt();
int m=cin.nextInt();
long sum=0;
for(int i=1;i<=n;i++) {
int x=cin.nextInt();
int y=cin.nextInt();
int z=cin.nextInt();
m-=x;
while(m<0) {
if(q.isEmpty()) {
sum=-1;
i=n+1;
break;
}
List<Integer> r=q.poll(); //前面价格最小的
int res=Math.min(r.get(1),-m); //这次使用r的数量
sum+=(long)res*r.get(0);
m+=res;
if(res!=r.get(1)) //要是还有剩余,将剩余数量再加入q中
q.add(fun(r.get(0),r.get(1)-res));
}
q.add(fun(y,z));
}
System.out.println(sum);
}
}
質問 H: 太陽
タイトルの意味: いくつかの線分と光源を与えます。光源によって検出できる線分の数を決定します。
アイデア: 計算幾何学
100% の考えはなく、他人にブロックされているかどうかは 30%、つまりジャッジの 2 倍だけです。
ディスプレイが遮られる可能性があると判断し、少なくとも高さが遮られる <ファイル<光源または遮られる>ファイル> 光源が 遮られる可能性がある
次はブロックされた線分の 2 つの端点です。光源に接続した後はブロックされた線分と交差できません。
この方法は、外積を行って2 つの線分が交差していないことを確認することです。また、それらの線分が互いに素であるためには、2 つの点の同じ側にある必要があります。
合格:
このアイデアの時間計算量は 30% になる可能性があり、最適なのは極角ソートです。
コアコード:
static double add(Node a,Node x,Node y) { //叉积
return (a.x-x.x)*(a.y-y.y)-(a.y-x.y)*(a.x-y.x);
}
static boolean solve(int i,int j) {
//只有高度合适的时候,i才有可能被j挡到
if((b[i]<=b[j] && b[j]<=y) || (y<=b[j] && b[j]<=b[i])) {
double h1 = add(new Node(a[j], b[j]), new Node(a[i], b[i]), new Node(x, y));
double h2 = add(new Node(a[j] + l[j], b[j]), new Node(a[i], b[i]), new Node(x, y));
if ((h1 * h2) < 0) return true;
h1 = add(new Node(a[j], b[j]), new Node(a[i] + l[i], b[i]), new Node(x, y));
h2 = add(new Node(a[j] + l[j], b[j]), new Node(a[i] + l[i], b[i]), new Node(x, y));
if ((h1 * h2) < 0) return true;
}
return false;
}
コード:
import java.util.*;
public class Main{
static int n,x,y;
static int a[]=new int[1000000],b[]=new int[1000000],l[]=new int[1000000];
static double add(Node a,Node x,Node y) { //叉积
return (a.x-x.x)*(a.y-y.y)-(a.y-x.y)*(a.x-y.x);
}
static boolean solve(int i,int j) {
if((b[i]<=b[j] && b[j]<=y) || (y<=b[j] && b[j]<=b[i])) {
double h1 = add(new Node(a[j], b[j]), new Node(a[i], b[i]), new Node(x, y));
double h2 = add(new Node(a[j] + l[j], b[j]), new Node(a[i], b[i]), new Node(x, y));
if ((h1 * h2) < 0) return true;
h1 = add(new Node(a[j], b[j]), new Node(a[i] + l[i], b[i]), new Node(x, y));
h2 = add(new Node(a[j] + l[j], b[j]), new Node(a[i] + l[i], b[i]), new Node(x, y));
if ((h1 * h2) < 0) return true;
}
return false;
}
public static void main(String[] args){
Scanner cin =new Scanner(System.in);
n=cin.nextInt();
x=cin.nextInt();
y=cin.nextInt();
for(int i=1;i<=n;i++) {
a[i]=cin.nextInt();
b[i]=cin.nextInt();
l[i]=cin.nextInt();
}
int sum=0;
for(int i=1;i<=n;i++) {
for(int j=1;j<=n;j++) {
if(i!=j && solve(i,j)) {
break;
}
if(j==n) sum++;
}
}
System.out.println(sum);
}
}
class Node{
public double x,y;
public Node(){}
public Node(double a,double b){
this.x=a;
this.y=b;
}
}
質問 I: タワー
サンプルがうまくいかない、何もない
合格: 0%
質問 J: 逆 XOR 01 文字列
タイトルの意味:
アイデア: 接頭辞の合計 + 回文文字列判定
この種の質問の考え方は、注意深く研究した後では間違っているでしょう
私の考え: 反 XOR 演算の後、文字列は 0 を中心とする奇数長の回文文字列、または偶数長の回文文字列のみになります。操作は 1 つだけで、他の操作は両側に 0 と 1 を加算するものである場合、回文文字列は指定された文字列内に存在する必要があります。
そのため、その中のテキスト部分文字列を見つけるには、中央展開方法を使用します。1 の数=左端点の左側にある 1 の数 + 右端点の右側にある 1 の数 + 回文文字列内の 1 の数 /2 を使用します。
すべてのケースで 1 の最小数を見つけるには、O (n の 2 乗) になります。
合格:
時間計算量は O(n 二乗)、つまり 60% です。
O (n) または O (nlogn) で回文の部分文字列 (最長ではない) を見つける方法があるかどうかはわかりません。もちろん、この考えが正しいという前提があります。
コード:
import java.util.*;
public class Main{
static String s;
static int f[]=new int[1000000];
static int n,mi=10000000;
static int get(int x,int y){ //前缀和
if(y<1 || x>n || x>y) return 0;
return f[y]-f[x-1];
}
static void solve(int l,int r){
while(l-1!=0 && r+1!=n+1 && s.charAt(l-1)==s.charAt(r+1)){
l--;
r++;
}
mi=Math.min(mi,get(l,r)/2+get(1,l-1)+get(r+1,n));
}
public static void main(String[] args){
Scanner cin =new Scanner(System.in);
s=cin.next();
n=s.length();
s=" "+s;
for(int i=1;i<=n;i++){
f[i]+=f[i-1]+(s.charAt(i)=='1'?1:0);
}
for(int i=1;i<=n;i++){
if(s.charAt(i)=='0')
solve(i,i);
if(i!=n && s.charAt(i)==s.charAt(i+1))
solve(i,i+1);
}
System.out.println(mi);
}
}