欢迎回来
A - BBQ Easy
题意:有
2N
2
N
根烤肉扦,第
i
i
个烤肉扦长度为,每一块肉需要横跨两根烤肉扦,长度为
1
1
。可以随意分配烤肉扦的顺序,询问最多同时可以烤多少块肉。
是整数
解答:将烤肉扦从小到大排序,下标为奇数的位置之和即为答案。
#include <bits/stdc++.h>
#define N 1050
using namespace std;
int a[N],n,ans;
inline int rd() {
int x=0,f=1;char ch=getchar();
while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
int main() {
n = rd() * 2;
for (int i=1;i<=n;i++) a[i] = rd();
sort(a+1,a+n+1);
for (int i=1;i<=n;i+=2) ans += a[i];
cout << ans << endl;
return 0;
}
B - Mysterious Light
题意:有一个边长为
N
N
的正三角形,顶点分别为、
b
b
、,有一束激光从
ab
a
b
边上,距离
a
a
为的位置,平行于
bc
b
c
射入正三角形。激光遇到三角形的三边或者已经走过的光路时,会发生镜面反射。询问最后激光回到出发点时,走过光路的长度。
2≤N≤1012,1≤X≤N−1
2
≤
N
≤
10
12
,
1
≤
X
≤
N
−
1
解答:答案为
3(N−gcd(N,X))
3
(
N
−
g
c
d
(
N
,
X
)
)
原图形走两步就会变成一个平行四边形,两边分别为
(X,N−X)
(
X
,
N
−
X
)
,这两步的长度为N
考虑这样一个长宽为
a
a
、平行四边形,设需要的步数为
F(a,b)
F
(
a
,
b
)
那么有这样一个关系式:
若
a>b
a
>
b
,
F(a,b)=F(b,a)
F
(
a
,
b
)
=
F
(
b
,
a
)
若
a=b
a
=
b
,
F(a,b)=a=b
F
(
a
,
b
)
=
a
=
b
若
a<b
a
<
b
,
F(a,b)=2a+F(b−a,b)
F
(
a
,
b
)
=
2
a
+
F
(
b
−
a
,
b
)
这样我们就已经得到一个log级别的递推算法了
我们还可以对这个式子化简
我们定义
phi(F(a,b))=a+b
p
h
i
(
F
(
a
,
b
)
)
=
a
+
b
当a
≠b
≠
b
时,
phi
p
h
i
每下降
x
x
,的值会增加
2x
2
x
当
a=b
a
=
b
时,
phi
p
h
i
每下降
2x
2
x
,
F
F
的值会增加
加上最开始走的两部,可以得知答案为
3(N−gcd(N,X))
3
(
N
−
g
c
d
(
N
,
X
)
)
#include <bits/stdc++.h>
using namespace std;
inline int rd() {
int x=0,f=1;char ch=getchar();
while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
int main() {
long long n,m;
cin >> n >> m;
cout << 3LL * (n-__gcd(n,m));
return 0;
}
C - Shorten Diameter
题意:给定一棵
N
N
个节点的树,至少移除多少个节点,使得该树的直径。
2≤N≤2000
2
≤
N
≤
2000
;
1≤K≤N−1
1
≤
K
≤
N
−
1
;
1≤Ai≤N
1
≤
A
i
≤
N
;
1≤Bi≤N
1
≤
B
i
≤
N
解答:若一个树的直径小于等于
K
K
,则一定存在一条边,使得删除这条边之后,以边的两个端点为根的子树深度分别小于等于和
⌊K−12⌋
⌊
K
−
1
2
⌋
。爆枚这条边再dfs判深度即可,时间复杂度
O(N2)
O
(
N
2
)
#include <bits/stdc++.h>
#define N 100050
#define INF (1<<29)
using namespace std;
inline int rd() {
int x=0,f=1;char ch=getchar();
while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
int a[N],b[N],n,k,tot;
vector<int> E[N];
void dfs(int u,int f,int d) {
if (d > k/2) return ;
tot++;
for (int i=0;i<(int)E[u].size();i++) {
int v = E[u][i];
if (v == f) continue;
dfs(v,u,d+1);
}
}
int main() {
n = rd(), k = rd();
for (int i=1;i<n;i++) {
int u = rd(), v = rd();
a[i] = u, b[i] = v;
E[u].push_back(v);
E[v].push_back(u);
}
int ans = INF;
if (k%2 == 0) {
for (int i=1;i<=n;i++) {
tot = 0;
dfs(i, i ,0);
ans = min(ans, n - tot);
}
} else {
for (int i=1;i<n;i++) {
tot = 0;
dfs(a[i], b[i], 0);
dfs(b[i], a[i], 0);
ans = min(ans, n - tot);
}
}
cout << ans << endl;
return 0;
}
D - Arrays and Palindrome
题意:
给定一个长度为
M
M
,和为的序列{
A
A
},需要重排并且构造一个数列{
B
B
}满足:
1、{}的元素和为
N
N
2、任何序列满足和
(2)
(
2
)
的都满足
(3)
(
3
)
(1)序列的这些元素是回文串:前
A1
A
1
个项,接下来
A2
A
2
项,接下来
A3
A
3
项……
(2)序列的这些元素是回文串:前
B1
B
1
个项,接下来
B2
B
2
项,接下来
B3
B
3
项……
(3)该序列的所有元素相同
1≤N≤105
1
≤
N
≤
10
5
1≤M≤100
1
≤
M
≤
100
Ai≤105
A
i
≤
10
5
解答:
转化一下模型,有一排
N
N
个点,长度为的回文串相当于连接
⌊L2⌋
⌊
L
2
⌋
条无向边,要求最后所有的点都在一个联通分量里面。
显然至少需要
N−1
N
−
1
条边,那么无解的情况我们就可以通过这个来判断了。如果
A
A
中的奇数项大于等于3,无解。
单独讨论一下的情况
当
M≥2
M
≥
2
时,将
A
A
中的奇数项移动到两端,,
BM=AM−1
B
M
=
A
M
−
1
,其余的
Bi=Ai
B
i
=
A
i
即可。
#include <bits/stdc++.h>
#define N 1000500
using namespace std;
int a[N],b[N],n,m,cnt,t;
inline int rd() {
int x=0,f=1;char ch=getchar();
while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
int main() {
n = rd(), m = rd();
for (int _=1;_<=m;_++) a[_] = rd();
sort(a+1,a+m+1, greater<int>() );
if (m==1) {
if (n==1) {printf("1\n1\n1\n"); return 0;}
printf("%d\n",a[1]);
printf("2\n");
printf("%d %d\n",a[1]-1,1);
return 0;
}
for (int _=1;_<=m;_++) (a[_]&1) ? ++cnt : 0;
if (cnt > 2) return puts("Impossible"), 0;
for (int _=2;_<=m-1;_++)
if (a[_]&1) (a[1]&1) ? swap(a[_],a[m]) : swap(a[_],a[1]);
for (int _=1;_<=m;_++) printf("%d%c",a[_],_==m?'\n':' ');
(a[1]==1) ? (t=2) : (t=1);
printf("%d\n",m-t+1);
memcpy(b,a,sizeof(a));
b[1]--, b[m]++;
for (int _=t;_<=m;_++) printf("%d%c",b[_],_==m?'\n':' ');
return 0;
}
E - BBQ Hard
题意:
给定
N
N
个数{}和{
B
B
},求
解答:
对 CAi+AjAi+Bi+Aj+Bj C A i + B i + A j + B j A i + A j 可以看做是平面直角坐标系中,从 (−Ai,−Bi) ( − A i , − B i ) 到 (Aj,Bj) ( A j , B j ) 最短路的数量。(只能从整点走到整点)
那么将每个 (−Ai,−Bi) ( − A i , − B i ) 作为起点,每个 (Ai,Bi) ( A i , B i ) 作为终点,最短路条数之和就能得到
减去 ∑ni=1CAi+Bi2(Ai+Bi) ∑ i = 1 n C 2 ( A i + B i ) A i + B i 后再除以二即可
#include <bits/stdc++.h>
#define N 4050
#define c 2010
#define mod 1000000007
using namespace std;
typedef long long LL;
int n,a[50*N],b[50*N],F[N][N],jc[5*N];
inline int rd() {
int x=0,f=1;char ch=getchar();
while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
inline void inc(int &x,int y) {x=(x+y)%mod;}
inline int qp(int a,int b) {
int ret = 1;
while (b) {
if (b&1) ret = 1LL * ret * a % mod;
b >>= 1, a = 1LL * a * a % mod;
}
return ret;
}
inline int C(int a,int b) {
return 1LL * jc[a] * qp(jc[b], mod-2) % mod * qp(jc[a-b], mod-2) % mod;
}
int main() {
n = rd();
for (int _=1;_<=n;_++) a[_] = rd(), b[_] = rd();
for (int _=1;_<=n;_++) F[c-a[_]][c-b[_]]++;
for (int x=c-2000;x<=c+2000;x++)
for (int y=c-2000;y<=c+2000;y++)
inc(F[x][y], F[x-1][y]), inc(F[x][y], F[x][y-1]);
jc[0] = 1;
for (int i=1;i<=8000;i++) jc[i] = 1LL * jc[i-1] * i % mod;
int ans = 0;
for (int _=1;_<=n;_++) inc(ans, F[ c+a[_] ][ c+b[_] ]);
for (int _=1;_<=n;_++) inc(ans, mod - C(2*a[_]+2*b[_], 2*a[_]) );
ans = 1LL * ans * qp(2, mod-2) % mod;
cout << ans << endl;
return 0;
}
F - Wide Swap
题意:
给定一个长度为
N
N
的排列,对于
i
i
,满足
j−i≥K
j
−
i
≥
K
且
|Pi−Pj|=1
|
P
i
−
P
j
|
=
1
就可以交换这两个元素,求字典序最的序列。
解答:
阅读和参考了官方题解以及
https://www.cnblogs.com/BearChild/p/7895719.html
令
Q
Q
为的转置,即
QPi=i
Q
P
i
=
i
。
由字典序的性质可得,当
Q
Q
字典序最小时,字典序最小。
对排列
P
P
,只有在两个元素相邻并且权值差不小于时才能交换。
由此得出一个性质,当两元素
Px
P
x
和
Py
P
y
(
x<y
x
<
y
)的权值差小于
K
K
时,无论怎么操作元素始终在
Py
P
y
元素前面。
显然,满足所有上述限制的排列,一定能够通过合法的交换得到。
那么对于权值差小于
K
K
的两个元素和
Py
P
y
,我们从
Px
P
x
向
Py
P
y
连一条边,那么一组合法解便满足该图的拓扑序。
这么连边的边数是
O(N2)
O
(
N
2
)
,下面优化连边:
形如A–>B, B–>C, A–>C的连边中,A–>C这条边显然在拓扑关系中无用。
我们考虑如何避免加入 A–>C 这种边:将
Pi
P
i
连向
(Pi−K,Pi)
(
P
i
−
K
,
P
i
)
和
(Pi,Pi+K)
(
P
i
,
P
i
+
K
)
两个区间下标最小的那一个即可。线段树维护区间最小值。
时间复杂度:
O(NlogN)
O
(
N
l
o
g
N
)
#include <bits/stdc++.h>
#define N 500050
#define INF (1<<29)
#define mid ((l+r)>>1)
#define ls l,mid,t<<1
#define rs mid+1,r,t<<1^1
using namespace std;
inline int rd() {
int x=0,f=1;char ch=getchar();
while (ch>'9'||ch<'0') {if(ch=='-')f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}
priority_queue<int, vector<int>, greater<int> >q;
int n,k,e[N],a[N],p[N],d[N];
int x,ll,rr,res;
int tr[4*N];
vector<int> E[N];
void query(int l,int r,int t) {
if (!tr[t]) return ;
if (l >= ll && r <= rr) {
if (res == 0 || p[ tr[t] ] < p[res] )
res = tr[t];
return ;
}
if (ll <= mid) query(ls);
if (rr > mid) query(rs);
}
void update(int l,int r,int t) {
if (l > x || r < x) return ;
if (l == r) {tr[t] = x; return ;}
update(ls), update(rs);
if (p[tr[t<<1]] < p[tr[t<<1^1]])
tr[t] = tr[t<<1];
else
tr[t] = tr[t<<1^1];
}
bool cmp(int p1,int p2) {return p[p1] < p[p2];}
int main() {
n = rd(), k = rd();
for (int _=1;_<=n;_++) p[_] = rd();
p[0] = INF;
for (int _=1;_<=n;_++) e[_] = _;
sort(e+1,e+n+1,cmp);
for (int _=n;_>=1;_--) {
x = e[_];
ll = x - k + 1, rr = x, res = 0;
query(1,n,1);
if (res)
E[x].push_back(res), d[res]++;
ll = x, rr = x + k - 1, res = 0;
query(1,n,1);
if (res)
E[x].push_back(res), d[res]++;
update(1,n,1);
}
for (int _=1;_<=n;_++) if (!d[_]) q.push(_);
for (int _=1;_<=n;_++) {
int x = q.top(); q.pop();
a[x] = _;
for (int i=0;i<(int)E[x].size();i++)
if (--d[ E[x][i] ]==0) q.push(E[x][i]);
}
for (int _=1;_<=n;_++) printf("%d\n",a[_]);
return 0;
}