Part.I Day10游记
今天都是些什么神仙题啊???
第一题似乎很毒瘤的样子,推了一发式子发现这是个二元二次的,于是想到用枚举一个,三分一个的做法来写,结果没开long long
。。。然后60→0。。。?
之后就在干第二题了。。。第二题我差点写出正解。。。结果不知道哪里打错了就半天调不过,然后自闭。。。
第三题我太懒了不想写缩点。。。
Part.II Day11游记
今天又讲课。
上午学习了一发Hash,KMP,AC自动机,ExKMP等一堆东西(然而在听AC自动机的时候我差点睡着。。。)。没开空调,太热了QAQ。。。讲课时外面是树剖现场~~(砍树)~~,树枝落下的声音一阵阵的。
下午学了SA,SAM,回文自动机(回文树),感觉还好(估计多校赛的时候我为了做题考场上自学了一发回文自动机。。。)。。。然而讲的都是神仙题。。。听了两道就跑了。。。
晚上写前一天的题解。第一题的数学公示够我受的了QAQ。。。
Part.III 题解
A.数学题(math)
题目
题目描述
输入
输入有多组测例,每组测例有一行,为4 个整数x1,y1, x2, y2,含义见题目描述。输入文件以EOF 结束。
输出
样例输入
3 0 1 2
6 0 4 0
样例输出
5
0
数据范围
分析
这是个论文题,论文是2009年国家集训队金斌的《欧几里得算法的应用》。
其中论文有一个小错,就是当 a ⃗ , b ⃗ \vec{a},\vec{b} a,b共线时,答案是 0 0 0,因为这时我们只需将向量做成反向即可。若当向量同向时我们取 λ 1 = ∣ a ⃗ ∣ gcd ( ∣ a ⃗ ∣ , ∣ b ⃗ ∣ ) , λ 2 = − ∣ b ⃗ ∣ gcd ( ∣ a ⃗ ∣ , ∣ b ⃗ ∣ ) \lambda_1=\frac{|\vec{a}|}{\gcd(|\vec a|,|\vec b|)},\lambda_2=-\frac{|\vec b|}{\gcd(|\vec a|,|\vec b|)} λ1=gcd(∣a∣,∣b∣)∣a∣,λ2=−gcd(∣a∣,∣b∣)∣b∣时,此时答案为 0 0 0;同理可得向量反向时的情况。
当两向量不共线时,有两个神仙结论可以被证明:
结论一:当向量 a ⃗ , b ⃗ \vec a,\vec b a,b的夹角大于 6 0 ∘ 60^\circ 60∘时,答案为 min ( a ⃗ , b ⃗ ) \min(\vec a,\vec b) min(a,b)。
结论二:设 ( λ 1 , λ 2 ) (\lambda_1,\lambda_2) (λ1,λ2)为所求的答案,则 ( λ 1 , λ 2 + k λ 1 ) (\lambda_1,\lambda_2+k\lambda_1) (λ1,λ2+kλ1)也是答案,其中 k k k为整数。
接下来是证明:(不想看可跳过)
结论一的证明:设 a ⃗ , b ⃗ \vec a,\vec b a,b的夹角为 α \alpha α。因为 cos 6 0 ∘ = 1 2 \cos 60^\circ=\frac{1}{2} cos60∘=21,所以 cos α < 1 2 \cos\alpha<\frac{1}{2} cosα<21。为了方便,此处约定 ∣ a ⃗ ∣ ≤ ∣ b ⃗ ∣ |\vec a|\le |\vec b| ∣a∣≤∣b∣。
所以: ∣ λ 1 a ⃗ + λ 2 b ⃗ ∣ 2 = ( λ 1 ∣ a ⃗ ∣ ) 2 + ( λ 2 ∣ b ⃗ ∣ ) 2 − 2 cos α λ 1 ∣ a ⃗ ∣ λ 2 ∣ b ⃗ ∣ ≥ ∣ λ 1 a ⃗ ∣ 2 + ∣ λ 2 b ⃗ ∣ 2 − 2 cos α ∣ λ 1 a ⃗ ∣ ∣ λ 2 b ⃗ ∣ ≥ ∣ λ 1 a ⃗ ∣ 2 + ∣ λ 2 b ⃗ ∣ 2 − ∣ λ 1 a ⃗ ∣ ∣ λ 2 b ⃗ ∣ ≥ ( ∣ λ 1 a ⃗ ∣ − ∣ λ 2 b ⃗ ∣ ) 2 + ∣ λ 1 a ⃗ ∣ ∣ λ 2 b ⃗ ∣ \begin{aligned} |\lambda_1\vec a+\lambda_2\vec b|^2&=(\lambda_1|\vec a|)^2+(\lambda_2|\vec b|)^2-2\cos\alpha\lambda_1|\vec a|\lambda_2|\vec b|\\&\ge|\lambda_1\vec a|^2+|\lambda_2\vec b|^2-2\cos\alpha|\lambda_1\vec a||\lambda_2\vec b|\\&\ge|\lambda_1\vec a|^2+|\lambda_2\vec b|^2-|\lambda_1\vec a||\lambda_2\vec b|\\&\ge(|\lambda_1\vec a|-|\lambda_2\vec b|)^2+|\lambda_1\vec a||\lambda_2\vec b|\end{aligned} ∣λ1a+λ2b∣2=(λ1∣a∣)2+(λ2∣b∣)2−2cosαλ1∣a∣λ2∣b∣≥∣λ1a∣2+∣λ2b∣2−2cosα∣λ1a∣∣λ2b∣≥∣λ1a∣2+∣λ2b∣2−∣λ1a∣∣λ2b∣≥(∣λ1a∣−∣λ2b∣)2+∣λ1a∣∣λ2b∣
当 λ 1 = 0 , λ 2 ̸ = 0 \lambda_1=0,\lambda_2\not= 0 λ1=0,λ2̸=0时, ( ∣ λ 1 a ⃗ ∣ − ∣ λ 2 b ⃗ ∣ ) 2 = ∣ λ 2 b ⃗ ∣ 2 ≥ ∣ b ⃗ ∣ 2 ≥ ∣ a ⃗ ∣ 2 (|\lambda_1\vec a|-|\lambda_2\vec b|)^2=|\lambda_2\vec b|^2\ge|\vec b|^2\ge |\vec a|^2 (∣λ1a∣−∣λ2b∣)2=∣λ2b∣2≥∣b∣2≥∣a∣2。
当 λ 1 ̸ = 0 , λ 2 = 0 \lambda_1\not=0,\lambda_2=0 λ1̸=0,λ2=0时, ( ∣ λ 1 a ⃗ ∣ − ∣ λ 2 b ⃗ ∣ ) 2 = ∣ λ 1 a ⃗ ∣ 2 ≥ ∣ a ⃗ ∣ 2 (|\lambda_1\vec a|-|\lambda_2\vec b|)^2=|\lambda_1\vec a|^2\ge|\vec a|^2 (∣λ1a∣−∣λ2b∣)2=∣λ1a∣2≥∣a∣2。
否则: ∣ λ 1 a ⃗ ∣ ∣ λ 2 b ⃗ ∣ ≥ ∣ a ⃗ ∣ ∣ b ⃗ ∣ ≥ ∣ a ⃗ ∣ 2 |\lambda_1\vec a||\lambda_2\vec b|\ge|\vec a||\vec b|\ge |\vec a|^2 ∣λ1a∣∣λ2b∣≥∣a∣∣b∣≥∣a∣2。
故当 α > 6 0 ∘ \alpha>60^\circ α>60∘时,答案为 ∣ a ⃗ ∣ 2 |\vec a|^2 ∣a∣2。
结论二的证明:设答案为 ∣ λ 1 a ⃗ + λ 2 b ⃗ ∣ 2 |\lambda_1\vec a+\lambda_2\vec b|^2 ∣λ1a+λ2b∣2,则答案也可以是 ∣ a ⃗ ( λ 1 − k λ 2 ) + ( b ⃗ + k a ⃗ ) λ 2 ∣ 2 |\vec a(\lambda_1-k\lambda_2)+(\vec b+k\vec a)\lambda_2|^2 ∣a(λ1−kλ2)+(b+ka)λ2∣2,即: ( λ 1 , λ 2 ) (\lambda_1,\lambda_2) (λ1,λ2)对应答案 ( λ 1 − k λ 2 , λ 2 ) (\lambda_1-k\lambda_2,\lambda_2) (λ1−kλ2,λ2)。且前一个不为0,则后一个也不为0。同理可证相反方向。
由结论一可知,我们必须想一个办法使两向量夹角增大。
盗一下论文的图:
设 O A ⃗ = a ⃗ , O B ⃗ = b ⃗ , B E ⊥ O A \vec {OA}=\vec a,\vec {OB}=\vec b,BE\perp OA OA=a,OB=b,BE⊥OA,且 O C ⃗ = k O A ⃗ , O D ⃗ = ( k + 1 ) O A ⃗ \vec{OC}=k\vec{OA},\vec{OD}=(k+1)\vec{OA} OC=kOA,OD=(k+1)OA。
若 ∣ O A ∣ < ∣ O E ∣ |OA|<|OE| ∣OA∣<∣OE∣,用三角形的内外角关系不难看出: max ( ∠ B C D , ∠ B D C ) > ∠ A O B \max(\angle BCD,\angle BDC)>\angle AOB max(∠BCD,∠BDC)>∠AOB,所以我们可以用更大的夹角的两边替换掉原来的。
我们发现这个算法的流程和扩展欧几里得算法类似,所以我们可以用类似扩展欧几里得的算法设计出一个求解这个问题的算法。
接下来是一些细节:
当向量的夹角大于 9 0 ∘ 90^\circ 90∘时,我们将其中一个向量反向,这样是对答案没有影响的且能保证夹角小于等于 9 0 ∘ 90^\circ 90∘。
若有一个向量为零向量,我们就强制返回最小值。
一点小技巧:若我们有向量 a ⃗ ( x 1 , y 1 ) , b ⃗ ( x 2 , y 2 ) \vec a(x_1,y_1),\vec b(x_2,y_2) a(x1,y1),b(x2,y2),则有 cos α ∣ a ⃗ ∣ ∣ b ⃗ ∣ = x 1 x 2 + y 1 y 2 \cos\alpha|\vec a||\vec b|=x_1x_2+y_1y_2 cosα∣a∣∣b∣=x1x2+y1y2,则有 cos α = x 1 x 2 + y 1 y 2 ∣ a ⃗ ∣ ∣ b ⃗ ∣ \cos\alpha=\frac{x_1x_2+y_1y_2}{|\vec a||\vec b|} cosα=∣a∣∣b∣x1x2+y1y2,所以我们有 O E = cos α ∣ b ⃗ ∣ = x 1 x 2 + y 1 y 2 ∣ a ⃗ ∣ OE=\cos\alpha|\vec b|=\frac{x_1x_2+y_1y_2}{|\vec a|} OE=cosα∣b∣=∣a∣x1x2+y1y2,所以有 k = ⌊ O E ∣ a ⃗ ∣ ⌋ = ⌊ x 1 x 2 + y 1 y 2 ∣ a ⃗ ∣ 2 ⌋ k=\lfloor\frac{OE}{|\vec a|}\rfloor=\lfloor\frac{x_1x_2+y_1y_2}{|\vec a|^2}\rfloor k=⌊∣a∣OE⌋=⌊∣a∣2x1x2+y1y2⌋。
参考代码
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
void ExGCD(ll x1,ll y1,ll x2,ll y2,ll &a,ll &b) {
ll x=(x1*x2+y1*y2),y=x1*x1+y1*y1,z=x2*x2+y2*y2;
if(x<0) {
ExGCD(-x1,-y1,x2,y2,a,b),a=-a;
return;
}
if(4*x*x<y*z||x==0) {
if(y>z)b=1;
else a=1;
return;
}
bool is_swap=false;
if(y<z)swap(x1,x2),swap(y1,y2),is_swap=true;
y=x1*x1+y1*y1,z=x2*x2+y2*y2;
ll t=x/z,t1=t+1;
if(x-t*z<=t1*z-x&&t)
ExGCD(x1-t*x2,y1-t*y2,x2,y2,a,b),b-=(t*a);
else ExGCD(x1-t1*x2,y1-t1*y2,x2,y2,a,b),b-=(t1*a);
if(is_swap)swap(a,b);
}
int main() {
// #ifdef LOACL
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
// #endif
freopen("math.in","r",stdin);
freopen("math.out","w",stdout);
ll x1,x2,y1,y2;
while(scanf("%lld %lld %lld %lld",&x1,&y1,&x2,&y2)!=EOF) {
ll a=0,b=0;
ExGCD(x1,y1,x2,y2,a,b);
ll t1=(x1*a+x2*b)*(x1*a+x2*b),t2=(y1*a+y2*b)*(y1*a+y2*b);
printf("%lld\n",t1+t2);
}
return 0;
}
B.挖宝藏(treasure)
题目
题目描述
输入
输出
输出一个整数,为矿工获得所有宝藏的最小代价。
样例输入
2 2 2
10 9
10 10
10 1
10 10
1 1 1
1 2 2
样例输出
30
数据范围&&样例解释
分析
由于每层是相对独立的,所以我们先考虑一层的情况,再考虑所有层组合的情况。
我们设状态 f ( x , y , S ) f(x,y,S) f(x,y,S)为当前在点 ( x , y ) (x,y) (x,y),已经挖出的宝藏集合为 S S S所消耗的最小体力。
考虑转移:当我们从一个方向过来,获得了 S ′ S' S′的宝藏,并经过 ( x , y ) (x,y) (x,y)从另一个方向上获得了 S ′ ′ S'' S′′的宝藏且 S ′ ∩ S ′ ′ = ∅ S'\cap S''=\varnothing S′∩S′′=∅,并回到 ( x , y ) (x,y) (x,y),那么不难列出这种状态转移: f ( x , y , S ′ ∪ S ′ ′ ) = min ( f ( x , y , S ′ ) + f ( x , y , S ′ ′ ) − a ( x , y ) ) f(x,y,S'\cup S'')=\min(f(x,y,S')+f(x,y,S'')-a(x,y)) f(x,y,S′∪S′′)=min(f(x,y,S′)+f(x,y,S′′)−a(x,y))。
但当我们没有获得宝藏并挖了一格呢?
这是有后效性的,我们就不好转移了。那么如何解决?
观察此时的状态转移方程:
f
(
x
′
,
y
′
,
S
)
=
min
(
f
(
x
,
y
,
S
)
+
a
(
x
′
,
y
′
,
S
)
)
f(x',y',S)=\min(f(x,y,S)+a(x',y',S))
f(x′,y′,S)=min(f(x,y,S)+a(x′,y′,S))。这有点像用SPFA求最短路时用的式子。(没错就是它)
所以我们这时可以使用SPFA来进行第二种转移。
考虑多层的情况。由于只能下不能上,所以我们倒着做。
那么如何把第 h + 1 h+1 h+1层的代价传给第 h h h层?,一个有用的方法就是建立一个超级宝藏,若额外的宝藏在 ( x , y ) (x,y) (x,y),则它的代价就是 f ( h + 1 , x , y , S h + 1 ) f(h+1,x,y,S_{h+1}) f(h+1,x,y,Sh+1),其中 S h + 1 S_{h+1} Sh+1是第 h + 1 h+1 h+1层的所有宝藏的全集。这样我们就能够做很多层的了。
参考代码
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int Maxn=10;
const int dir[][2]={{0,1},{0,-1},{-1,0},{1,0}};
const int INF=0x3f3f3f3f;
int H,N,M;
int A[Maxn+3][Maxn+3][Maxn+3];
vector<pair<int,int> > t[Maxn+3];
int pos[Maxn+3][Maxn+3][Maxn+3];
int f[Maxn+3][Maxn+3][Maxn+3][(1<<Maxn)+3];
bool inq[Maxn+3][Maxn+3];
void SPFA(int h,int s) {
queue<pair<int,int> > q;
memset(inq,false,sizeof inq);
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++)
if(f[h][i][j][s]<INF)
q.push(make_pair(i,j)),inq[i][j]=true;
while(!q.empty()) {
pair<int,int> now=q.front();
int x=now.first,y=now.second;
q.pop(),inq[x][y]=false;
for(int i=0;i<4;i++) {
int tx=x+dir[i][0],ty=y+dir[i][1];
if(tx<1||tx>N||ty<1||ty>M)
continue;
if(f[h][tx][ty][s]>f[h][x][y][s]+A[h][tx][ty]) {
f[h][tx][ty][s]=f[h][x][y][s]+A[h][tx][ty];
if(!inq[tx][ty])
q.push(make_pair(tx,ty)),inq[tx][ty]=true;
}
}
}
}
int main() {
// #ifdef LOACL
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
// #endif
freopen("treasure.in","r",stdin);
freopen("treasure.out","w",stdout);
scanf("%d %d %d",&H,&N,&M);
for(int i=1;i<=H;i++)
for(int j=1;j<=N;j++)
for(int k=1;k<=M;k++)
scanf("%d",&A[i][j][k]);
for(int i=1;i<=H;i++) {
int k,x,y;
scanf("%d",&k);
for(int j=0;j<k;j++) {
scanf("%d %d",&x,&y);
t[i].push_back(make_pair(x,y));
pos[i][x][y]=(1<<j);
}
}
memset(f,0x3f,sizeof f);
memset(f[H+1],0,sizeof f[H+1]);
for(int h=H;h>=1;h--) {
int now=(1<<t[h].size()),pre=(1<<(t[h+1].size()+1))-1;
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++)
f[h][i][j][now+pos[h][i][j]]=f[h+1][i][j][pre]+A[h][i][j],
f[h][i][j][pos[h][i][j]]=A[h][i][j];
for(int s=1;s<(1<<(t[h].size()+1));s++) {
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++)
for(int s1=(s-1)&s;;s1=(s1-1)&s) {
if(s1==0)break;
f[h][i][j][s]=min(f[h][i][j][s],f[h][i][j][s1]+f[h][i][j][s-s1]-A[h][i][j]);
}
SPFA(h,s);
}
}
int ans=INF;
for(int i=1;i<=N;i++)
for(int j=1;j<=M;j++)
ans=min(ans,f[1][i][j][(1<<(t[1].size()+1))-1]);
printf("%d\n",ans);
return 0;
}
C.理想城市(city)
题目
题目描述
输入
第1 行为一个正整数N,为理想城区块的数目。
第2 行到第N+1 行,每行有两个非负整数。第i+2 行为第i 个区块的坐标vi = (xi, yi)。
输出
输出仅一行一个正整数,为S 的值。由于S 的值可能较大,你只需输出S mod 10^9。
样例输入
11
2 5
2 6
3 3
3 6
4 3
4 4
4 5
4 6
5 3
5 4
5 6
样例输出
174
数据范围
分析
对于一棵树,我们可以很容易求出其两两点对间的距离之和,然而对于一个图就不太方便了。
然而这是个方格图。。。
但由于方格图的性质,我们可以把相连的一整行缩成一个点,再把相邻的列连起来。这样所有的节点都可以连成一个树,树的边权就是这整行的点数。
那么每两个点中,所有的点对都会经过树边,所以我们就将问题转化为了一个简单的树形DP。
注意应对行列各缩一次点。
参考代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int Maxn=100000;
const int Hash_Mod=3e6+7;
const ll Mod=1e9;
const int INF=2147483647;
int N,R,C;
struct Point {
int x,y;
};
Point A[Maxn+5];
int siz[Maxn+5];
ll ans;
ll hs[Hash_Mod+5];
int vis[Hash_Mod+5];
inline int get_val(int x,int y) {
return (1LL*(x-1)*C+y);
}
inline int Hash(int x,int y) {
ll tmp=get_val(x,y);
int t=tmp%Hash_Mod;
while(hs[t]&&hs[t]!=tmp)
t=(t+1)%Hash_Mod;
return t;
}
struct Edge {
int to;
Edge *nxt;
};
Edge pool[4*Maxn+5];
Edge *ecnt,*G[Maxn+5];
inline void addedge(int u,int v) {
Edge *i=++ecnt;
i->to=v,i->nxt=G[u],G[u]=i;
}
void DFS(int u,int fa) {
for(Edge *i=G[u];i!=NULL;i=i->nxt) {
int v=i->to;
if(v==fa)continue;
DFS(v,u);
siz[u]+=siz[v];
}
for(Edge *i=G[u];i!=NULL;i=i->nxt) {
int v=i->to;
if(v==fa)continue;
ans=(ans+1LL*(N-siz[v])*siz[v])%Mod;
}
}
struct DSU {
int fa[Maxn+5];
void init(int n) {
for(int i=0;i<=n;i++)
fa[i]=i;
}
int find(int x) {
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
void merge(int x,int y) {
int fx=find(x),fy=find(y);
if(fx!=fy)fa[fy]=fx;
}
bool same(int x,int y) {
return find(x)==find(y);
}
};
DSU t1,t2;
void Build_Graph(int v1,int v2) {
for(int i=1;i<=N;i++)
G[i]=NULL,siz[i]=1;
ecnt=&pool[0];
t1.init(N),t2.init(N);
for(int i=1;i<=N;i++) {
int x=A[i].x,y=A[i].y;
if(x+v1>R||y+v2>C)continue;
int tmp1=Hash(x,y),tmp2=Hash(x+v1,y+v2);
if(hs[tmp2]) {
int f1=t1.find(vis[tmp1]),f2=t1.find(vis[tmp2]);
if(t1.same(f1,f2))continue;
t1.merge(f1,f2);
siz[f1]+=siz[f2],siz[f2]=0;
}
}
for(int i=1;i<=N;i++) {
int x=A[i].x,y=A[i].y;
if(x+v2>R||y+v1>C)continue;
int tmp1=Hash(x,y),tmp2=Hash(x+v2,y+v1);
if(hs[tmp2]) {
int f1=t1.find(vis[tmp1]),f2=t1.find(vis[tmp2]);
if(t2.same(f1,f2))continue;
t2.merge(f1,f2),addedge(f1,f2),addedge(f2,f1);
}
}
}
int main() {
// #ifdef LOACL
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
// #endif
freopen("city.in","r",stdin);
freopen("city.out","w",stdout);
scanf("%d",&N);
int minx=INF,miny=INF;
for(int i=1;i<=N;i++) {
scanf("%d %d",&A[i].x,&A[i].y);
minx=min(minx,A[i].x),miny=min(miny,A[i].y);
R=max(R,A[i].x),C=max(C,A[i].y);
}
minx--,miny--;
for(int i=1;i<=N;i++)
A[i].x-=minx,A[i].y-=miny;
R-=minx,C-=miny;
for(int i=1;i<=N;i++) {
int val=Hash(A[i].x,A[i].y);
hs[val]=get_val(A[i].x,A[i].y),vis[val]=i;
}
Build_Graph(1,0);
DFS(t1.find(1),0);
Build_Graph(0,1);
DFS(t1.find(1),0);
printf("%lld\n",ans);
return 0;
}