Solution
T1
考虑 O ( n 2 ) O(n^2) O(n2)暴力: 枚举 a , b a,b a,b,然后求出对应的 c c c,更新答案。
这种做法会T掉。虽然我们可以将时间复杂度优化到 O ( n ) O(n) O(n),可事实上暴力大力剪枝+卡常就能过。
①对于一个 a a a,我们将 b b b先从 0 − 3 0-3 0−3枚举一遍,如果这些 b b b都不行,那么这个 a a a就不行了;直接break;
②得到一个 c c c的特解后,每次将 c c c加 4 4 4而不是加 1 1 1,这样可以保证对于每一个 c c c,都存在一个自然数 b b b;
③把;改成;
④在循环的 i n t int int前加上个 r e g i s t e r register register;
⑤不要开 l o n g l o n g long\ long long long。
于是这样不开O2都能过。
T2
裸的超纲整数拆分问题。
首先,我们考虑 d p dp dp。 d p dp dp有两种做法:
①无限背包(以
1
−
n
1-n
1−n中每一个数为一个物品,价值均为
1
1
1,背包容量为
n
n
n且必须装满)
②另外一种
d
p
dp
dp。
状态设计:
d
p
i
,
j
:
dp_{i,j}:
dpi,j: 目前选了
i
i
i个数,和为
j
j
j。
状态转移:
d
p
i
,
j
=
d
p
i
,
j
−
i
+
d
p
i
−
1
,
j
−
1
dp_{i,j}=dp_{i,j-i}+dp_{i-1,j-1}
dpi,j=dpi,j−i+dpi−1,j−1
这个状态转移式是什么意思呢?
d
p
i
,
j
−
i
dp_{i,j-i}
dpi,j−i可以认为是将
d
p
i
,
j
−
i
dp_{i,j-i}
dpi,j−i表示的序列的所有数加
1
1
1,
d
p
i
−
1
,
j
−
1
dp_{i-1,j-1}
dpi−1,j−1表示将
d
p
i
−
1
,
j
−
1
dp_{i-1,j-1}
dpi−1,j−1所表示的序列最后面多上一个
1
1
1。相当于我们可以将一个序列全局加
1
1
1,或者在最后面添上一个
1
1
1,显然这个序列一直是有序的。
这两种
d
p
dp
dp都是
O
(
n
2
)
O(n^2)
O(n2)的。它们各有所长——
①假设物品的数量为
x
x
x,那么复杂度是
O
(
x
n
)
O(xn)
O(xn)。即,如果物品数量不多,这个
d
p
dp
dp很不错!
②假设物品最多能有
x
x
x个,那么复杂度是
O
(
x
n
)
O(xn)
O(xn)。即,如果物品最多能有的数量不多,那么这个
d
p
dp
dp很棒!
考虑根号分治。对于所有不大于 n \sqrt n n的数,我们作第一种 d p dp dp;对于剩余的数做第二种 d p dp dp。可以发现,对于①的物品的数量只有 n \sqrt n n个,对于②而言,由于所选的所有数都不小于 n \sqrt n n,所以物品最多只能有 O ( n ) O(\sqrt n) O(n)个。
最后将两个 d p dp dp数组合并起来即可得到答案。时间复杂度 O ( n n ) O(n \sqrt n) O(nn)。
根号分治牛逼!
T3
首先,我们发现一个神奇的东西: n ≤ 100 n≤100 n≤100
所以复杂度中一定有 n 3 n^3 n3这一个部分,但是这个数值很小,所以后面还会再套上一个什么东西,比如 O ( n 3 m ) O(n^3 \sqrt m) O(n3m)之类的。但是, k k k的值似乎非常大!所以,我们是不是要想到……
⌊ \lfloor ⌊ 矩乘,以 n n n为矩阵的边长,以 t t t为矩阵的指数 ⌉ \rceil ⌉
想到这里,我们要考虑如何设计出一个可以用矩阵乘法优化的 d p dp dp。 d p i , j dp_{i,j} dpi,j表示当前到了第 i i i个节点,一共使用了 j j j次魔法。状态转移显然: d p i , j = min 1 ≤ j ≤ n d p i ′ , j − 1 + c o s t ( i ′ , i ) dp_{i,j}=\min_{1≤j≤n} {dp_{i',j-1}+cost(i',i)} dpi,j=1≤j≤nmindpi′,j−1+cost(i′,i)
这里 c o s t ( i ′ , i ) cost(i',i) cost(i′,i)表示从 i ′ i' i′经过一些道路到达 i i i,在这个期间使用恰好一次魔法时的最小代价。现在关键在于如何求出这一个 c o s t ( i ′ , i ) cost(i',i) cost(i′,i)。
我们枚举在从 i ′ i' i′到 i i i的过程中使用魔法的是哪条边。假设这条有向边是 u − > v u->v u−>v,那么此时的花费就是 d i s ( i ′ , u ) − e d g e ( u , v ) + d i s ( v , i ) dis(i',u)-edge(u,v)+dis(v,i) dis(i′,u)−edge(u,v)+dis(v,i),这里 d i s ( u , v ) dis(u,v) dis(u,v)表示 u u u到 v v v的最短路径, e d g e ( u , v ) edge(u,v) edge(u,v)表示 u − > v u->v u−>v这条边的边权。
于是我们可以在 O ( n 3 ) O(n^3) O(n3)的代价下通过 F l o y d Floyd Floyd求出任意两个节点之间的 d i s dis dis,在 O ( n 2 m ) O(n^2 m) O(n2m)的代价下求出 c o s t cost cost,然后在 O ( n 3 log k ) O(n^3 \log k) O(n3logk)的代价下用矩阵乘法来加速这个转移。具体来说,我们快速转移的是一个长度为 n n n的数组,假设当前为第 t t t轮,那么数组中的第 p p p个数就表示 d p p , t dp_{p,t} dpp,t。注意矩阵乘法是广义的,但是同样拥有结合律,从而可以使用快速幂来优化矩乘。
总时间复杂度 O ( n 2 ( m + n log k ) O(n^2(m+n\log k) O(n2(m+nlogk)。
Summary
一套不错的题目,可是真的不适合普及组。
第一题是一道比较水的数学题,甚至放了剪枝、卡常的小暴力过。
第二题是一道套路的
d
p
dp
dp题,巧用根号分治,结合两种
d
p
dp
dp的优缺点分治,达到平衡,时间复杂度优化到了
O
(
n
n
)
O(n \sqrt n)
O(nn)。
第三题仍然是一道
d
p
dp
dp题,考验了数据范围观察能力(即经验),还有广义矩阵乘法的巧用以及最短路。
Code
A
#include <bits/stdc++.h>
#define rg register
using namespace std;
int n,maxsumv=-1,maxminv=-1,a,b,c;
int minn(int x,int y,int z)
{
return min(min(x,y),z);
}
signed main()
{
cin>>n;
for (rg int i=0;7*i<=n;i++)
{
int k=n-7*i,pos;
for (rg int j=0;j<=3;j++)
{
if ((k-3*j)%4==0&&k-3*j>=0)
{
pos=j;
break;
}
}
for (int j=pos;3*j<=k;j+=4)
{
int first=i,second=(k-3*j)/4,third=j;
if (minn(first,second,third)>maxminv)
maxminv=minn(first,second,third),maxsumv=first+second+third,a=first,b=second,c=third;
else if (minn(first,second,third)==maxminv)
{
if (first+second+third>maxsumv)
maxsumv=first+second+third,a=first,b=second,c=third;
}
}
}
if (a*7+b*4+c*3==n) cout<<a<<' '<<b<<' '<<c<<endl;
else cout<<-1<<endl;
return 0;
}
B
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=100005;
int n,p,m;ll ans=0;
int f[100005],g[405][100005];
int getmod(int x)
{
return x-(x/p)*p;
}
signed main()
{
cin>>n>>p;
m=sqrt(n)+1;
f[0]=1;
for (int i=1;i<m;i++)
{
for (int j=i;j<=n;j++) f[j]=getmod(f[j-i]+f[j]);
}
g[0][0]=1;
for (int i=1;i<m;i++)
{
for (int j=i;j<=n;j++)
{
g[i][j]=g[i][j-i];
if (j>=m) g[i][j]=getmod(g[i][j]+g[i-1][j-m]);
}
}
int ans=0;
for (int i=0;i<=n;i++)
{
int sum=0;
for (int j=0;j<m;j++) sum=(sum+g[j][n-i])%p;
ans=(ans+1ll*f[i]*sum)%p;
}
cout<<ans<<endl;
return 0;
}
C
#include <bits/stdc++.h>
#define int long long
#define cost fjioadf
#define inf 4000000000000000007
using namespace std;
int n,m,kkk;
int GA[105][105],cost[105][105];
struct node{int u,v,w;}a[10005];
struct Matrix
{
int a[110][110];
Matrix() {for (int i=0;i<=n;i++) for (int j=0;j<=n;j++) a[i][j]=inf;}
}base;
Matrix operator * (const Matrix &x,const Matrix &y)
{
Matrix res;
for (int i=0;i<=n;i++)
{
for (int j=0;j<=n;j++)
{
res.a[i][j]=inf;
for (int k=0;k<=n;k++)
res.a[i][j]=min(res.a[i][j],x.a[i][k]+y.a[k][j]);
}
}
return res;
}
Matrix quick_power(Matrix x,int y)
{
Matrix res=x;y--;
for (;y;y=y>>1,x=(x*x))
{
if (y&1) res=(res*x);
}
return res;
}
signed main()
{
cin>>n>>m>>kkk;
for (int i=1;i<=n;i++)
{
for (int j=1;j<=n;j++)
{
if (i!=j) GA[i][j]=inf;
else GA[i][j]=0;
}
}
for (int i=1;i<=m;i++)
cin>>a[i].u>>a[i].v>>a[i].w,GA[a[i].u][a[i].v]=a[i].w;
for (int k=1;k<=n;k++)
{
for (int i=1;i<=n;i++)
{
for (int j=1;j<=n;j++)
{
if (GA[i][j]>GA[i][k]+GA[k][j])
GA[i][j]=GA[i][k]+GA[k][j];
}
}
}
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) cost[i][j]=inf;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
for (int k=1;k<=m;k++)
{
int v=a[k].u;
int u=a[k].v;
cost[i][j]=min(cost[i][j],GA[j][v]+GA[u][i]-a[k].w);
}
for (int i=0;i<=n;i++)
{
for (int j=0;j<=n;j++)
{
if (i==0&&j!=0) base.a[i][j]=inf;
else if (j==0)
{
if (i==0||i==1) base.a[i][j]=0;
else base.a[i][j]=inf;
}
else base.a[i][j]=cost[n-j+1][n-i+1];
}
}
if (kkk==0)
{
cout<<GA[1][n]<<endl;
return 0;
}
Matrix res=quick_power(base,kkk+1);
cout<<res.a[n][0]<<endl;
return 0;
}