五子棋对弈
题意:5*5棋盘下五子棋,平局的情况数
解法:直接搜索然后计数
训练士兵
题意:n名士兵,编号1-n,每名士兵需要训练ci次,士兵训练的费用为pi,除了单独训练士兵还可以用费用S训练全体士兵,求最小训练花费
思路:士兵按照训练次数的降序排序,然后计算前缀和,找到第一个前缀和小于S的位置k,那么就有
t
o
t
a
l
C
o
s
t
=
S
⋅
c
k
+
1
+
∑
i
=
1
k
p
i
(
c
i
−
c
k
+
1
)
totalCost = S\cdot c_{k + 1}+\sum_{i=1}^k p_i(c_i - c_{k + 1})
totalCost=S⋅ck+1+i=1∑kpi(ci−ck+1)
代码:
#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
#define ll long long
const int N = 1e5 + 11;
struct Soder
{
int price, cycle;
bool operator<(const Soder &a) const
{
return cycle > a.cycle;
}
} soder[N];
ll presum[N]; // 前缀和
ll ans;
int main()
{
int n;
ll S;
int cycle[N];
scanf("%d%ld", &n, &S);
for (int i = 1; i <= n; i++)
scanf("%d%d", &soder[i].price, &soder[i].cycle);
sort(soder + 1, soder + n + 1);
for (int i = 1; i <= n; i++)
presum[i] = presum[i - 1] + soder[i].price;
int idx = lower_bound(presum + 1, presum + n + 1, S) - presum;
ans = S*soder[idx].cycle;
for (int i = 1; i < idx; i++)
ans += ((ll)soder[i].price)*(soder[i].cycle - soder[idx].cycle);
printf("%lld\n", ans);
return 0;
}
团建
题意:给定两棵树,大小分别为n和m,每个树上的节点都有一个权值(第一棵树ai,第二棵树bi),然后从两棵树的1号节点出发求最长的公共前缀。(特殊约束:每个节点的叶子节点的权重各不相同)
思路:用两个有限队列存储树的节点,优先队列排序第一关键字是节点深度,第二关键字是节点权值。每次比对两个优先队列的队头,若相等(权重、深度都相同),则更新答案,并拓展两个节点的儿子入队;若不相等弹出深度较小的节点,若深度一样则弹出权重较小的节点。
代码:
#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
const int N = 2e5 + 11;
int n, m;
int pVal[N][2];
int edgeCnt, head[N][2],fa[N][2];
int ans;
struct Edge
{
int pre, to;
Edge(int a = 0, int b = 0) : pre(a), to(b) {}
} edge[N << 2];
struct Node
{
int depth, val, idx;
Node(int a = 0, int b = 0, int c = 0) : depth(a), val(b), idx(c) {}
bool operator<(const Node &a) const
{
if (depth != a.depth)
return depth > a.depth;
return val > a.val;
}
bool operator!=(const Node &a) const
{
return depth != a.depth || val != a.val;
}
};
void addedge(int a, int b, int idx)
{
edge[++edgeCnt] = (Edge){head[a][idx], b};
head[a][idx] = edgeCnt;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%d", &pVal[i][0]);
for (int i = 1; i <= m; i++)
scanf("%d", &pVal[i][1]);
for (int i = 1; i < n; ++i)
{
int a, b;
scanf("%d%d", &a, &b);
addedge(a, b, 0);
addedge(b, a, 0);
}
for (int i = 1; i < m; ++i)
{
int a, b;
scanf("%d%d", &a, &b);
addedge(a, b, 1);
addedge(b, a, 1);
}
priority_queue<Node> q[2];
for (int i = 0; i < 2; i++)
{
while (!q[i].empty())
q[i].pop(); // 初始化
q[i].push(Node(1, pVal[1][i],1));
}
while (!q[0].empty() && !q[1].empty())
{
Node a = q[0].top(), b = q[1].top();
if (a != b)
{
if (a < b)
q[1].pop();
else
q[0].pop();
continue;
}
q[1].pop();
q[0].pop();
ans = a.depth;
for(int i = head[a.idx][0]; i; i = edge[i].pre)
{
int v = edge[i].to;
if(v != fa[a.idx][0]){
q[0].push(Node(a.depth + 1, pVal[v][0], v));
fa[v][0] = a.idx;
}
}
for(int i = head[b.idx][1]; i; i = edge[i].pre)
{
int v = edge[i].to;
if(v != fa[b.idx][1]){
q[1].push(Node(b.depth + 1, pVal[v][1], v));
fa[v][1] = b.idx;
}
}
}
printf("%d\n", ans);
}
成绩统计
题意:给定n个正整数,在给定一个正整数k和T,前x个数中能否挑出k个数使得这k个数方差小于T,若能则输出最小的x。
思路:二分答案,可行的x是单调的,故而二分枚举x。判断x是否可解的方法:将1~x个数排序,然后以1为起点,窗口大小为k,滑动统计窗口内的方差。解的判断的复杂度是O(nlogn),二分的复杂度是(logn)故而最终复杂度是
O
(
n
l
o
g
2
(
n
)
)
O(nlog^2(n))
O(nlog2(n))
注意数据范围(爆int了)
#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
#include <queue>
#include <algorithm>
using namespace std;
#define ll long long
const int N = 1e5 + 11;
ll n, k, T;
ll val[N];
ll nowVal[N];
ll ans;
bool isLegal(int s)
{
bool flag = false;
if (s < k)
return flag;
memcpy(nowVal, val, sizeof(ll) * (s + 1));
sort(nowVal + 1, nowVal + s + 1);
ll sum = 0, prodsum = 0;
for (int i = 1; i <= s; ++i)
{
sum += nowVal[i];
prodsum += nowVal[i] * nowVal[i];
if (i > k)
{
sum -= nowVal[i - k];
prodsum -= nowVal[i - k] * nowVal[i - k];
}
if (i >= k)
{
double sigmaProd = (double)prodsum / k - (double)sum * sum / k / k;
if (sigmaProd <= T)
{
flag = true;
break;
}
}
}
return flag;
}
int solve(int l, int r)
{
if (l == r)
return isLegal(l) ? l : -1;
int mid = (l + r) >> 1;
if (isLegal(mid))
return solve(l, mid);
else
return solve(mid + 1, r);
}
int main()
{
// freopen("data.in","r",stdin);
scanf("%ld%ld%ld", &n, &k, &T);
for (int i = 1; i <= n; i++)
scanf("%ld", &val[i]);
ans = solve(k, n);
printf("%ld\n", ans);
return 0;
}
因数统计
题意:给定n个正整数的序列
{
a
i
}
n
\{a_i\}_n
{ai}n,给出合法的四元组
(
i
,
j
,
k
,
l
)
(i,j,k,l)
(i,j,k,l),其中
i
,
j
,
k
,
l
i,j,k,l
i,j,k,l各不相同。四元组在
a
k
=
0
(
m
o
d
a
i
)
,
a
l
=
0
(
m
o
d
a
j
)
a_k =0(mod\ a_i),a_l=0 (mod\ a_j)
ak=0(mod ai),al=0(mod aj)时为合法。
思路:
先考虑统计合法的因子对数即
(
i
,
k
)
(i,k)
(i,k)对数,即满足
a
k
=
0
(
m
o
d
a
i
)
a_k =0(mod\ a_i)
ak=0(mod ai)的对数。注意到数据范围时1e5,故而采用遍历每个数倍数的方法可以统计得到合法
(
i
,
k
)
(i,k)
(i,k)对数。
我们记合法对数的集合为
A
=
{
(
i
,
k
)
∣
a
k
=
0
(
m
o
d
a
i
)
}
A=\{(i,k)|a_k =0(mod\ a_i)\}
A={(i,k)∣ak=0(mod ai)},记
A
′
=
{
(
j
,
l
)
∣
a
l
=
0
(
m
o
d
a
j
)
}
A'=\{(j,l)|a_l =0(mod\ a_j)\}
A′={(j,l)∣al=0(mod aj)}。显然
A
=
A
′
A=A'
A=A′,两者做直积的
B
=
{
(
i
,
j
,
k
,
l
)
∣
a
k
=
0
(
m
o
d
a
i
)
,
a
l
=
0
(
m
o
d
a
j
)
}
B=\{(i,j,k,l)|a_k =0(mod\ a_i),a_l =0(mod\ a_j)\}
B={(i,j,k,l)∣ak=0(mod ai),al=0(mod aj)}。显然集合B中的元素都满足因数的条件,但是不满足
i
,
j
,
k
,
l
i,j,k,l
i,j,k,l各不相同的条件,故而可以在集合B的基础上利用容斥原理计算满足条件的四元组数量。记
B
(
i
,
k
)
=
{
(
i
,
j
,
k
,
l
)
∈
B
∣
i
=
k
}
B_{(i,k)}=\{(i,j,k,l)\in B|i=k\}
B(i,k)={(i,j,k,l)∈B∣i=k},同理有
B
(
i
,
l
)
B_{(i,l)}
B(i,l),
B
(
j
,
k
)
B_{(j,k)}
B(j,k),
B
(
j
,
l
)
B_{(j,l)}
B(j,l),进一步记
B
(
i
,
k
)
,
(
j
,
l
)
=
{
(
i
,
j
,
k
,
l
)
∈
B
∣
i
=
k
,
j
=
l
}
B_{(i,k),(j,l)}=\{(i,j,k,l)\in B|i=k,j=l\}
B(i,k),(j,l)={(i,j,k,l)∈B∣i=k,j=l},同理有
B
(
i
,
l
)
,
(
j
,
k
)
B_{(i,l),(j,k)}
B(i,l),(j,k)(
B
(
i
,
k
)
,
(
j
,
k
)
B_{(i,k),(j,k)}
B(i,k),(j,k),
B
(
i
,
l
)
,
(
j
,
l
)
B_{(i,l),(j,l)}
B(i,l),(j,l)为空集)
故而最终的集合为
c
a
r
d
(
B
′
)
=
c
a
r
d
(
B
)
−
c
a
r
d
(
B
(
i
,
k
)
)
−
c
a
r
d
(
B
(
i
,
l
)
)
−
c
a
r
d
(
B
(
j
,
k
)
)
−
c
a
r
d
(
B
(
j
,
l
)
)
+
c
a
r
d
(
B
(
i
,
k
)
,
(
j
,
l
)
)
+
c
a
r
d
(
B
(
i
,
l
)
,
(
j
,
k
)
)
card(B')=card(B)-card(B_{(i,k)})-card(B_{(i,l)})-card(B_{(j,k)})-card(B_{(j,l)})+card(B_{(i,k),(j,l)})+card(B_{(i,l),(j,k)})
card(B′)=card(B)−card(B(i,k))−card(B(i,l))−card(B(j,k))−card(B(j,l))+card(B(i,k),(j,l))+card(B(i,l),(j,k))。
所以解题的关键在于
B
(
i
,
k
)
B_{(i,k)}
B(i,k),
B
(
i
,
l
)
B_{(i,l)}
B(i,l),
B
(
j
,
k
)
B_{(j,k)}
B(j,k),
B
(
j
,
l
)
B_{(j,l)}
B(j,l),
B
(
i
,
k
)
,
(
j
,
l
)
B_{(i,k),(j,l)}
B(i,k),(j,l)以及
B
(
i
,
l
)
,
(
j
,
k
)
B_{(i,l),(j,k)}
B(i,l),(j,k)集合元素数量的统计。
下面介绍
B
(
i
,
k
)
B_{(i,k)}
B(i,k),
B
(
i
,
l
)
B_{(i,l)}
B(i,l)元素个数的统计,
B
(
j
,
k
)
B_{(j,k)}
B(j,k),
B
(
j
,
l
)
B_{(j,l)}
B(j,l)的统计类似。
我们考虑一下计数的顺序,
B
(
i
,
k
)
B_{(i,k)}
B(i,k)所涉及到的四元组可以表示为
(
i
,
j
,
i
,
l
)
(i,j,i,l)
(i,j,i,l),故而我们通过枚举
i
i
i,计算每一个
i
i
i对应的
(
j
,
l
)
(j,l)
(j,l)的个数即可,显然就是
a
i
a_i
ai倍数个数的平方,记为
n
i
,
(
i
,
k
)
n_{i,(i,k)}
ni,(i,k)。
我们做一些记号:
m
l
t
i
mlt_i
mlti表示序列中
a
i
a_i
ai倍数的个数,
f
a
c
t
o
r
i
factor_i
factori表示
a
i
a_i
ai因数的个数。(这两个量在统计
B
B
B的时候可以一起统计得到)
故而
n
i
,
(
i
,
k
)
=
m
l
t
i
∗
m
l
t
i
n_{i,(i,k)}=mlt_i*mlt_i
ni,(i,k)=mlti∗mlti,
c
a
r
d
(
B
(
i
,
k
)
)
=
∑
j
=
1
n
n
j
,
(
i
,
k
)
card(B_{(i,k)})=\sum\limits_{j=1}^{n}n_{j,(i,k)}
card(B(i,k))=j=1∑nnj,(i,k)。
B
(
i
,
l
)
B_{(i,l)}
B(i,l)涉及到的四元组可以表示为
(
i
,
j
,
k
,
i
)
(i,j,k,i)
(i,j,k,i),同样的枚举
i
i
i,计算对应的
(
j
,
k
)
(j,k)
(j,k)数量即可,即有
n
i
,
(
i
,
l
)
=
m
l
t
i
∗
f
a
c
t
o
r
i
n_{i,(i,l)}=mlt_i*factor_i
ni,(i,l)=mlti∗factori,
c
a
r
d
(
B
(
i
,
l
)
)
=
∑
j
=
1
n
n
j
,
(
i
,
l
)
card(B_{(i,l)})=\sum\limits_{j=1}^{n}n_{j,(i,l)}
card(B(i,l))=j=1∑nnj,(i,l)。
接下来是
B
(
i
,
k
)
,
(
j
,
l
)
B_{(i,k),(j,l)}
B(i,k),(j,l),显然有
c
a
r
d
(
B
(
i
,
k
)
,
(
j
,
l
)
)
=
c
a
r
d
(
A
)
card(B_{(i,k),(j,l)})=card(A)
card(B(i,k),(j,l))=card(A),这个在统计
B
B
B时就可以得出。
最后时
B
(
i
,
l
)
,
(
j
,
k
)
B_{(i,l),(j,k)}
B(i,l),(j,k),此时显然有
a
i
=
a
j
a_i=a_j
ai=aj,故而就是在相同的值的元素中选两个的排列,统计给定序列中每个不同的值出现的次数记为
b
i
b_i
bi,总共有N个不同的值,则
c
a
r
d
(
B
(
i
,
l
)
,
(
j
,
k
)
)
=
∑
i
=
1
N
(
b
i
−
1
)
∗
b
i
card(B_{(i,l),(j,k)})=\sum\limits_{i=1}^{N}(b_i-1)*b_i
card(B(i,l),(j,k))=i=1∑N(bi−1)∗bi
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<set>
#define ll long long
using namespace std;
const int N=1e5+11;
int n;
int val[N];
ll valcnt[N],fctcnt[N],multcnt[N];
ll twocnt,ans;
int read(){
int num=0,flag=1;char s=getchar();
for(;(s>'9'||s<'0')&&s!='-';s=getchar());
if(s=='-') flag=-1,s=getchar();
for(;s>='0'&&s<='9';s=getchar()) num=num*10+s-'0';
return num*flag;
}
int main(){
// freopen("19706.in","r",stdin);
// freopen(".out","w",stdout);
n = read();
int maxval = 1;
int minval = N;
for(int i = 1; i <= n; ++i){
val[i] = read();
maxval = max(maxval,val[i]);
minval = min(minval,val[i]);
valcnt[val[i]]++;
}
for(int i = minval; i <= maxval; ++i){
if(valcnt[i]==0)
continue;
fctcnt[i] += valcnt[i] - 1;
multcnt[i] += valcnt[i] - 1;
for(int j = 2*i; j <= maxval; j+=i){
fctcnt[j] += valcnt[i];
multcnt[i] += valcnt[j];
}
}
for(int i = minval; i <= maxval; ++i){
twocnt += fctcnt[i]*valcnt[i];
ans = ans - valcnt[i]*multcnt[i]*multcnt[i];
ans = ans - valcnt[i]*multcnt[i]*fctcnt[i]*2;
ans = ans - valcnt[i]*fctcnt[i]*fctcnt[i];
ans = ans + valcnt[i]*(valcnt[i]-1);
}
ans += twocnt * twocnt ;
ans += twocnt;
printf("%lld",ans);
}
封印宝石
题意:将n个宝石放入n个箱子中,每个宝石有一个权重,将宝石放入箱子里需要消耗体力,例如将第i个宝石放入第j个箱子中需要消耗体力
i
−
j
,
i
≥
j
i-j,i\ge j
i−j,i≥j,初始时有体力k。同时需要约束相邻的箱子不能放入相同权重的宝石,求最后箱子中宝石的最大字典序。
思路:对于第i个箱子而言就是查询[i,i+k_left]区间中可用宝石的最大值,如果前一个箱子中的值和查询到的最大值相等,则需要考虑查询次大值(权重与最大值不同),同时需要注意当两个宝石权重相同时,位置靠前的更优。
代码如下:
#include<bits/stdc++.h>
#define ls s<<1
#define rs s<<1|1
using namespace std;
const int N = 1e5 + 11;
int n,k;
int val[N];
int ans[N];
struct Node{
int id,val;
Node():id(0),val(-1){}
Node(int _id, int _val):id(_id),val(_val){}
bool operator <(const Node& a)const{
return val == a.val ? id < a.id : val > a.val;
}
};
struct Tree{
int l,r;
Node mx1,mx2;
}tree[N<<2];
int read(){
char s = getchar(); int flag = 1, now = 0;
while(s != '-' && (s > '9' || s < '0')) s = getchar();
if(s == '-') {flag = -1; s = getchar();}
while(s >= '0' && s <= '9'){
now = now * 10 + s - '0';
s = getchar();
}
return now * flag;
}
void update(int s){
Node tmp[4];
tmp[0] = tree[ls].mx1;
tmp[1] = tree[ls].mx2;
tmp[2] = tree[rs].mx1;
tmp[3] = tree[rs].mx2;
sort(tmp, tmp + 4);
tree[s].mx1 = tmp[0];
for(int i = 1; i < 4; ++i)
if(tmp[i].val != tmp[0].val){
tree[s].mx2 = tmp[i];
break;
}
}
void build(int s, int l, int r){
tree[s].l = l; tree[s].r = r;
if(l == r){
tree[s].mx1 = Node(l,val[l]);
tree[s].mx2 = Node();
return;
}
int mid = l + r >> 1;
build(ls, l, mid);
build(rs, mid+1, r);
update(s);
}
Tree findmax(int s, int l, int r){
if(l <= tree[s].l && tree[s].r <= r)
return tree[s];
Tree lmax, rmax, now;
int mid = tree[s].l + tree[s].r >> 1;
if(l <= mid)
lmax = findmax(ls, l, r);
if(r > mid)
rmax = findmax(rs, l, r);
Node tmp[4];
tmp[0] = lmax.mx1;
tmp[1] = lmax.mx2;
tmp[2] = rmax.mx1;
tmp[3] = rmax.mx2;
sort(tmp, tmp + 4);
now.mx1 = tmp[0];
for(int i = 1; i < 4; ++i)
if(tmp[i].val != tmp[0].val){
now.mx2 = tmp[i];
break;
}
return now;
}
void changeVal(int s, int id, int val){
if(tree[s].l == tree[s].r && tree[s].l == id){
tree[s].mx1 = Node(id, -1);
return;
}
int mid = tree[s].l + tree[s].r >> 1;
if(id <= mid)
changeVal(ls, id, val);
else
changeVal(rs, id, val);
update(s);
}
int main(){
n = read(); k = read();
for(int i = 1; i <= n; ++i){
val[i] = read();
}
build(1,1,n);
for(int i = 1; i <= n; ++i){
int l = i, r = min(n, i + k);
Tree node = findmax(1,l,r);
Node now;
if(node.mx1.val != ans[i-1])
now = node.mx1;
else
now = node.mx2;
ans[i] = now.val;
if(ans[i] != -1) {
k -= now.id - i;
changeVal(1,now.id,-1);
}
printf("%d ",ans[i]);
}
}