A. Coins
最优的策略一定是:当有至少 k 枚硬币面朝下时,则选 k 枚面朝下的硬币去抛掷(任意k 枚都可以);如果不足 k 枚面朝下,则在选择所有面朝下的硬币的基础上再额外选择若干面朝上的银币。
于是有动态规划,记
d
[
i
]
[
j
]
d[i][j]
d[i][j]表示抛掷了
i
i
i次后,有
j
j
j枚硬币面朝上的概率。他们应该满足
d
[
i
]
[
0
]
+
d
[
i
]
[
1
]
+
.
.
.
+
d
[
i
]
[
n
]
=
1
d[i][0]+d[i][1]+...+d[i][n]=1
d[i][0]+d[i][1]+...+d[i][n]=1。转移时,考虑从当前状态
(
i
,
j
)
(i,j)
(i,j)出发,抛掷的
k
k
k枚硬币的所有可能结果:分别有
0
0
0至
k
k
k 枚面朝上。其中
k
k
k 枚硬币抛掷后有
l
l
l 枚面朝上的概率为
C
k
l
/
2
k
C_k^{l}/2^k
Ckl/2k。时间复杂度
O
(
n
∗
m
∗
k
)
O(n*m*k)
O(n∗m∗k)。
#include<bits/stdc++.h>
using namespace std;
double d[200][200],p[200],c[200][200];
int main()
{
c[0][0]=1;
for(int i=1;i<=100;i++)
{
c[i][0]=1;
for(int j=1;j<=i;j++)c[i][j]=c[i-1][j-1]+c[i-1][j];
}
p[0]=1;
for(int i=1;i<=100;i++)p[i]=p[i-1]/2;
int T;cin>>T;
while(T--)
{
int n,m,t;
scanf("%d%d%d",&n,&m,&t);
memset(d,0,sizeof d);
d[0][0]=1;
for(int i=0;i<m;i++)
{
for(int j=0;j<=n;j++)
{
if(d[i][j]==0)continue;
for(int k=0;k<=t;k++)
{
if(n-j>=t)d[i+1][j+k]+=d[i][j]*c[t][k]*p[t];
else d[i+1][j-(t-(n-j))+k]+=d[i][j]*c[t][k]*p[t];
}
}
}
double ans=0;
for(int i=1;i<=n;i++)ans+=d[m][i]*i;
printf("%.3lf\n",ans);
}
return 0;
}
B. The Difference
签到题
C. The Number Triangle
假设
S
[
i
]
[
j
]
S[i][j]
S[i][j]是从
(
i
,
j
)
(i,j)
(i,j)位置向上走出来的最优路径(路径权值和最大)的最小字符串(字典序意义下)。那么对于固定的
i
i
i,考虑所有
S
[
i
]
[
j
]
S[i][j]
S[i][j]的字符串之间大小关系,并赋予他们排序数组
r
a
n
k
[
i
]
[
j
]
rank[i][j]
rank[i][j]。
于是我们可以利用
r
a
n
k
[
i
−
1
]
[
∗
]
rank[i-1][*]
rank[i−1][∗]去维护出来
r
a
n
k
[
i
]
[
∗
]
rank[i][*]
rank[i][∗],因为
S
[
i
]
[
j
]
S[i][j]
S[i][j]总是对应
r
[
i
]
[
j
]
r[i][j]
r[i][j](在位置
(
i
,
j
)
(i,j)
(i,j)的字符)与某个
S
[
i
−
1
]
[
∗
]
S[i-1][*]
S[i−1][∗]拼接出来的字符串,所以他们的大小关系可以利用
r
a
n
k
[
i
−
1
]
[
∗
]
rank[i-1][*]
rank[i−1][∗]计算出来。
#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
const int MAX=1510;
const double PI=acos(-1.0);
typedef long long ll;
int a[MAX][MAX];
int d[MAX][MAX];
char c[MAX][MAX];
int Rank[MAX][MAX];
vector<int>v;
int n;
int cmp(const int&x,const int &y)
{
if(Rank[n][x]!=Rank[n][y])return Rank[n][x]<Rank[n][y];
return x<y;
}
int main()
{
int T;
cin>>T;
while(T--)
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
{
char op[3];
scanf("%d%s",&a[i][j],op);
c[i][j]=op[0];
}
Rank[1][1]=0;
for(int i=2;i<=n;i++)
{
v.clear();
for(int j=1;j<=i;j++)
{
d[i][j]=max(d[i-1][j-1],d[i-1][j])+a[i][j];
Rank[i][j]=MOD;
if(j>1&&d[i][j]==d[i-1][j-1]+a[i][j])
Rank[i][j]=min(Rank[i][j],(c[i][j]-'a')*10000+Rank[i-1][j-1]);
if(j<i&&d[i][j]==d[i-1][j]+a[i][j])
Rank[i][j]=min(Rank[i][j],(c[i][j]-'a')*10000+Rank[i-1][j]);
v.push_back(Rank[i][j]);
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
for(int j=1;j<=i;j++)Rank[i][j]=lower_bound(v.begin(),v.end(),Rank[i][j])-v.begin();
}
v.clear();
for(int i=1;i<=n;i++)
{
if(d[n][i]==*max_element(d[n]+1,d[n]+n+1))v.push_back(i);
}
sort(v.begin(),v.end(),cmp);
for(int i=0;i<v.size();i++)printf("%d%c",v[i],i==v.size()-1?'\n':' ');
}
return 0;
}
D. Fence Building
最优策略总是在选定
n
n
n个点后,两两连线,且满足任意三条线不交于一点。
尝试统计总的结点个数
A
(
n
)
A(n)
A(n)与独立线段(包括圆弧上的
n
n
n段小弧)的总个数
B
(
n
)
B(n)
B(n),然后利用欧拉公式就可以得到答案
A
n
s
(
n
)
=
B
(
n
)
−
A
(
n
)
+
1
Ans(n)=B(n)-A(n)+1
Ans(n)=B(n)−A(n)+1。
任意四个点,会形成一个交点,并贡献额外的 2 条独立线段。所以
A
(
n
)
=
n
+
C
n
4
A(n)=n+C_n^4
A(n)=n+Cn4,而
B
(
n
)
=
n
+
2
∗
C
n
4
+
C
n
2
B(n)=n+2*C_n^4+C_n^2
B(n)=n+2∗Cn4+Cn2,所以最后答案为
C
n
2
+
C
n
4
+
1
C_n^2+C_n^4+1
Cn2+Cn4+1。
E. Friends
官方题解:对于 p p p个函数记他们形成的集合 V = { 0 , 1 , . . . , p − 1 } V=\{0,1,...,p-1\} V={0,1,...,p−1}。那么每一个 valuable circle of friends 形成了 V V V的一个子集,这些集合全体形成了 V V V的幂集的一个子集 E E E。问题变成找在 E E E 中同时出现次数不少于 l a m b d a = [ ( p + 1 ) / 2 ] lambda=[(p+1)/2] lambda=[(p+1)/2]的所有极大集合。
这实际上是一个经典的关联式规则学习问题。利用先验算法可以很高效的解决。
具体来说,依次找出所有大小为
1
,
2
,
3
,
.
.
.
1, 2, 3, ...
1,2,3,...的满足在
E
E
E 中同时出现的次数不少于
l
a
m
b
d
a
lambda
lambda的所有集合,依次记录为
L
(
1
)
,
L
(
2
)
,
L
(
3
)
,
.
.
.
L(1), L(2), L(3), ...
L(1),L(2),L(3),...。用
L
(
i
)
L(i)
L(i)去推
L
(
i
+
1
)
L(i+1)
L(i+1):选取
L
(
i
)
L(i)
L(i)中一个方案
U
U
U,
U
U
U 的大小为
i
i
i;向
U
U
U 中尝试加入新的元素
e
e
e,形成
W
=
U
+
{
e
}
W=U+\{e\}
W=U+{e},检查
W
W
W 的所有大小为
i
i
i 的子集是否在
L
(
i
)
L(i)
L(i)中;对于筛选出来的
W
W
W,再去
E
E
E 中检验是否合法。
利用位运算(这里需要拆成 2 个 64 位整数来表示)可以加速判断一个集合是不是另外一个集合的子集。这便可以解决这一题。
F. Gathering Minerals
官方题解:首先注意到这一题答案不会很大,因为所有时间都是
10
10
10的倍数,实际上最大用时除以
10
10
10 大概不会超过
750
750
750。所以可以想办法在本地跑出来所有时间
t
t
t可以获得的最大矿物量,并提交一个带有上述计算出来的数据的程序(也就是说可以打表)。
所以说,这一题对时间效率实际上是几乎没有限制的。
再注意到一开始的资源配置是
1
1
1个基地,
4
4
4个工人,
6
6
6份供给和
50
50
50份矿物。那么就有了一个比较容易估计出来的最优策略(虽然它的正确性并不容易说清楚)。
第一阶段,以建立尽可能多的基地为唯一目标。
第二阶段,考虑制造供给,和制造新的工人为目标(注意二者的关系,供给只有在制造新的工人的时候需要,他们二者又同时需要矿物)。
第三阶段,全心全意挖矿。
相邻阶段之间的分界线,以及第二阶段的分配,需要通过搜索来考虑所有可能的细节。具体来说,对于每一个当前状态,除了需要维护此刻的基地个数,工人个数,供给量还有矿物量,还需要维护之后
12
12
12个时间点(因为所有耗时都是
10
10
10的倍数,所以这里以每
10
s
10s
10s为一个时间点)会额外得到的工人数。
对于同一时刻的两个不同的状态,可以通过一些方法去对他们进行比较,并淘汰掉一定不优秀的那些状态策略。例如,如果其中有一个状态之后
12
12
12个时间点的物资都不劣于另一个状态,则后者一定没有存在的必要,可以被淘汰掉。更强力的淘汰方法可以单独对最近的若干个时间点(比如最近的
2
2
2个时间点)做出一些相对较优的、手动构造性的策略,从而更精细的去比较和淘汰状态。精细的处理可以使这一题几乎在
1
1
1秒内搜出来所有情况。
最后注意,本题矿物的规模到达了
64
64
64位无符号整数的极限,实际上注意到他们都是偶数,可以全部除以二,从而确保中间过程的计算不会超过
64
64
64位无符号整数。
G. The Mountain
题目给出的所需求面积的图形,可以被拆分位若干个梯形(包括退化成的三角形)的并,分别求每一个梯形的面积并累加就可以了。
H. Count Numbers
记 F [ i ] F[i] F[i]表示数位和为 i i i的不含 0 整数有多少个,再记 G [ i ] G[i] G[i]表示数位和为 i i i的不含 0整数的和。
那么 F [ i ] = ∑ F [ i − j ] F[i] = \sum F[i-j] F[i]=∑F[i−j], G [ i ] = ∑ ( 10 ∗ G [ i − j ] + j ∗ F [ i − j ] ) G[i] =\sum (10*G[i-j]+j*F[i-j]) G[i]=∑(10∗G[i−j]+j∗F[i−j]),其中 j j j枚举了最低位的数字,并从 1 遍历到 9。不妨同时维护相邻的 9 个位置的值(共 18 个),则可以构造18阶矩阵 A,实现它的快速转移。整个问题的答案也对应了 A 的 a b a^b ab次幂。可以利用矩阵乘法的结合律实现它的快速计算。
F[8] F[7] F[6] F[5] F[4] F[3] F[2] F[1] F[0] G[8] G[7] G[6] G[5] G[4] G[3] G[2] G[1] G[0]
伴随矩阵A:
1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0
1 0 1 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0
1 0 0 1 0 0 0 0 0 3 0 0 0 0 0 0 0 0
1 0 0 0 1 0 0 0 0 4 0 0 0 0 0 0 0 0
1 0 0 0 0 1 0 0 0 5 0 0 0 0 0 0 0 0
1 0 0 0 0 0 1 0 0 6 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 1 0 7 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 1 8 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 9 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 10 1 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 10 0 1 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 10 0 0 1 0 0 0 0 0
0 0 0 0 0 0 0 0 0 10 0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 0 0 10 0 0 0 0 1 0 0 0
0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 0 0 1
0 0 0 0 0 0 0 0 0 10 0 0 0 0 0 0 0 0
PS: a b a^b ab可能非常大,于是用JAVA写的。
import java.util.*;
import java.math.*;
public class Main {
static long MOD;
static class lenka
{
long a[][]=new long[20][20];
lenka()
{
for(int i=0;i<18;i++)
{
for(int j=0;j<18;j++)a[i][j]=0;
}
}
}
static lenka cla(lenka a,lenka b)
{
lenka c=new lenka();
for(int i=0;i<18;i++)
{
for(int j=0;j<18;j++)
{
for(int k=0;k<18;k++)
{
c.a[i][j]+=a.a[i][k]*b.a[k][j]%MOD;
c.a[i][j]%=MOD;
}
}
}
return c;
}
static long POW(BigInteger n)
{
lenka a=new lenka();
lenka res=new lenka();
for(int i=0;i<18;i++)res.a[i][i]=1;
for(int i=0;i<9;i++)
{
a.a[i][0]=1;
a.a[i][9]=i+1;
}
for(int i=1;i<9;i++)
{
a.a[i-1][i]=1;
a.a[i+8][i+9]=1;
}
for(int i=9;i<18;i++)a.a[i][9]=10;
while(n.compareTo(BigInteger.ZERO)>0)
{
if(n.mod(new BigInteger("2")).compareTo(BigInteger.ONE)==0)res=cla(a,res);
a=cla(a,a);
n=n.divide(new BigInteger("2"));
}
long d[]={128,64,32,16,8,4,2,1,1,23817625,2165227,196833,17891,1625,147,13,1,0};
long ans=0;
for(int i=0;i<18;i++)
{
ans+=(d[i]%MOD)*res.a[i][17]%MOD;
ans%=MOD;
}
return ans;
}
public static void main(String args[])
{
Scanner cin=new Scanner(System.in);
int T=cin.nextInt();
while((T--)>0)
{
BigInteger a=cin.nextBigInteger();
int b=cin.nextInt();
MOD=cin.nextLong();
System.out.println(POW(a.pow(b)));
}
}
}
I. A Possible Tree
思路:带权并查集。r[i]表示i到p[i]路径上所有边权的异或值。
#include<bits/stdc++.h>
using namespace std;
const int MAX=1e5+10;
const int MOD=1e9+7;
const double PI=acos(-1.0);
typedef long long ll;
int p[MAX],r[MAX];
int f(int x)
{
if(p[x]==x)return x;
int fa=p[x];
p[x]=f(p[x]);
r[x]^=r[fa];
return p[x];
}
int main()
{
int T;
cin>>T;
while(T--)
{
int n,c;
scanf("%d%d",&n,&c);
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
}
for(int i=0;i<=n;i++)
{
p[i]=i;
r[i]=0;
}
int ans=c+1;
for(int i=1;i<=c;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
if(ans!=c+1)continue;
int fx=f(x),fy=f(y);
if(fx==fy&&(r[x]^r[y])!=z)ans=i;
else
{
p[fx]=fy;
r[fx]=r[x]^r[y]^z;
}
}
printf("%d\n",ans-1);
}
return 0;
}
J. Lowest Common Ancestors
不妨先以
1
1
1号点为根,记
F
(
u
,
v
)
F(u,v)
F(u,v)是
u
u
u和
v
v
v在以
1
1
1为根的树内的
L
C
A
LCA
LCA。
具体来说,依然考虑以 1 为根的有根树,记
T
x
Tx
Tx是以
x
x
x为根的子树,并记
A
n
s
(
x
)
Ans(x)
Ans(x)是以
x
x
x为根的情况下的答案。对于每一个结点
x
x
x分成三部分来计算答案:
(i)对于
u
u
u 和
v
v
v中一个在
T
x
Tx
Tx内,一个在
T
x
Tx
Tx外的情况,他们在
A
n
s
(
x
)
Ans(x)
Ans(x)中贡献的值为
x
x
x。
(ii)如果有
u
u
u和
v
v
v同时在
T
x
Tx
Tx内,他们在
A
n
s
(
x
)
Ans(x)
Ans(x)中贡献的值就是
F
(
u
,
v
)
F(u,v)
F(u,v)。
(iii)如果
u
u
u和
v
v
v都在
T
x
Tx
Tx外,考虑从根一路走过来他们所在的位置,如果他们同时属于根到
x
x
x的路径上某一个分叉内,则他们贡献的值依然是
F
(
u
,
v
)
F(u,v)
F(u,v),否则考察两个分叉结点中高度较大的那一个。
先线性的求出来所有
F
(
u
,
v
)
F(u,v)
F(u,v),上述的三部分可以分别利用三次 DFS 遍历来实现。在(i)和(ii)中,分别要维护子树内完全出现的询问对的
L
C
A
LCA
LCA和,以及才出现一次的询问个数。而(iii)则需要先处理高度小的结点,并将对应部分的权值从父节点的编号修改为子节点的编号。
整个算法是线性的。
PS:时限给的有点小,977ms勉强过。
#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
const int MAX=1e5+10;
const double PI=acos(-1.0);
typedef long long ll;
struct EDG{int x,to,next;}ed[2*MAX];
int head[MAX],tot=0;
void addEDG(int x,int y)
{
ed[tot].to=y;
ed[tot].next=head[x];
head[x]=tot++;
}
vector<int>E[MAX];
struct lenka{int x,y;}a[MAX];
int p[MAX],v[MAX],LCA[MAX],fa[MAX];
int f(int x){return p[x]==x?x:p[x]=f(p[x]);}
void dfs(int k)//利用dfs和并查集线性求LCA
{
v[k]=1;
for(int i=head[k];i!=-1;i=ed[i].next)
{
int nex=ed[i].to;
if(nex==fa[k])continue;
fa[nex]=k;
dfs(nex);
p[f(nex)]=f(k);
}
for(int i=E[k].size()-1;i>=0;i--)
{
int j=E[k][i];
if(k==a[j].x&&v[a[j].y])LCA[j]=f(a[j].y);
if(k==a[j].y&&v[a[j].x])LCA[j]=f(a[j].x);
}
}
ll A[MAX],B[MAX],C[MAX];
void dfs1(int k)
{
for(int i=head[k];i!=-1;i=ed[i].next)
{
int nex=ed[i].to;
if(nex==fa[k])continue;
dfs1(nex);
B[k]+=B[nex]-C[nex];
}
}
void dfs2(int k)
{
A[k]+=A[fa[k]];
for(int i=head[k];i!=-1;i=ed[i].next)
{
int nex=ed[i].to;
if(nex==fa[k])continue;
A[nex]+=(B[k]-B[nex]+C[nex])*k;
dfs2(nex);
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int n,m;
scanf("%d%d",&n,&m);
assert(1<=n&&n<=100000);
tot=0;
for(int i=1;i<=n;i++)
{
E[i].clear();
head[i]=-1;
p[i]=i;
v[i]=A[i]=B[i]=C[i]=0;
}
for(int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
addEDG(x,y);
addEDG(y,x);
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&a[i].x,&a[i].y);
E[a[i].x].push_back(i);
E[a[i].y].push_back(i);
}
dfs(1);
for(int i=1;i<=m;i++)
{
A[1]+=LCA[i];
A[LCA[i]]-=LCA[i];
B[a[i].x]++;
B[a[i].y]++;
B[LCA[i]]--;
C[LCA[i]]++;
}
dfs1(1);
dfs2(1);
for(int i=1;i<=n;i++)printf("%lld%c",A[i]+B[i]*i,i==n?'\n':' ');
}
return 0;
}
K. Sum of the Line
考虑基于容斥原理的计算过程。如果
T
T
T中所有位置
T
(
r
,
c
)
=
c
T(r,c)=c
T(r,c)=c总成立,那么答案为
1
2
+
2
2
+
.
.
.
+
k
2
1^2+2^2+...+k^2
12+22+...+k2。
下面考虑删去
T
(
r
,
c
)
=
0
T(r,c)=0
T(r,c)=0的位置造成的代价。
考虑若干两两不同的素数
p
1
,
p
2
,
.
.
.
,
p
u
p_1, p_2, ..., p_u
p1,p2,...,pu并记
x
=
p
1
∗
p
2
.
.
.
∗
p
u
x=p_1*p_2...*p_u
x=p1∗p2...∗pu。则由容斥原理,上述答案需要减去
(
−
1
)
u
+
1
(-1)^{u+1}
(−1)u+1倍的
x
2
(
1
2
+
2
2
+
.
.
.
+
[
k
/
x
]
2
)
x^2(1^2+2^2+...+[k/x]^2)
x2(12+22+...+[k/x]2)。在
k
≤
1
0
8
k≤10^8
k≤108下,
u
u
u 不超过
9
9
9,所以只需要枚举
2
9
2^9
29种可能即可。
#include<bits/stdc++.h>
using namespace std;
const int MOD=998244353;
const int MAX= 1e4+100;
typedef long long ll;
int all=0,pr[MAX+100]; //素数表
bool isp[MAX+10];
vector<int>p;
void init()
{
all = 0;
memset(isp,0,sizeof isp);
isp[1]=1;
for(int i=2;i<=MAX;i++)
{
if(!isp[i])pr[all++] = i;
for(int j=0;j<all;j++)
{
long long t = 1LL*pr[j]*i ;
if(t<=MAX)
{
isp[t] = true;
if(i%pr[j]==0)break;
}
else break;
}
}
return ;
}
void div(int x) //分解成素数
{
for(int i = 0; i < all && x > 1; i++)
{
if(x%pr[i]==0)p.push_back(pr[i]);
while( x % pr[i] == 0)x /= pr[i];
}
if(x > 1)p.push_back(x);
}
ll cla(ll n)//计算1^2+2^2+3^2+....+n^2
{
ll ans=n*(n+1)/2;
if(ans%3==0)
{
ans/=3;
return (ans%MOD)*((2*n+1)%MOD)%MOD;
}
return (ans%MOD)*(((2*n+1)/3)%MOD)%MOD;
}
ll f(int x)
{
ll ans=1;
for(int i=0;i<p.size();i++,x/=2)if(x%2)ans*=p[i];
return ans;
}
int main()
{
init();
int T;cin>>T;
while(T--)
{
int k;
scanf("%d",&k);
p.clear();
div(k);
ll ans=cla(k);
for(int i=1;i<(1<<(p.size()));i++)
{
ll a=f(i);
if(__builtin_popcount(i)%2==0)ans+=(a*a%MOD)*cla(k/a)%MOD;//__builtin_popcount(i)计算i的二进制数中1的个数
else ans-=(a*a%MOD)*cla(k/a)%MOD;
ans%=MOD;
ans=(ans+MOD)%MOD;
}
printf("%lld\n",ans);
}
return 0;
}