本场链接:AtCoder Beginner Contest 204
D - Cooking
问题等价于把所有元素分成两组,使两组的和中的较大者超过总和的一半(上取整)且最小。
做一个可行性dp
,找出一个最小的可以表达出来的和且超过总和的一半(上取整)即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
const int N = 105,M = 1e5+7;
int a[N],f[N][M];
int main()
{
int n;scanf("%d",&n);
forn(i,1,n) scanf("%d",&a[i]);
int sum = 0;forn(i,1,n) sum += a[i];
f[1][a[1]] = 1;f[1][0] = 1;
forn(i,2,n)
{
forn(j,0,sum)
{
f[i][j] |= f[i - 1][j];
if(j + a[i] <= sum) f[i][j + a[i]] |= f[i - 1][j];
}
}
int res = sum;
forn(j,(sum + 1) / 2,sum) if(f[n][j]) res = min(res,j);
printf("%d
",res);
return 0;
}
E - Rush Hour 2
很自然的会想到能不能通过一些巧妙的建图来使得题目所需要答案等价于新图上的最短路,但是会发现下取整这个操作非常难办,于是只能考虑优化这个操作:还是一样跑最短路,设当前是使用(u)更新(v),假设(dist[u])是从(1->u)的最短时间,现在要求(v)的时间,如果在(t)时刻出发走到(v)的话,那么(dist[v] = t + c[i] + d[i] / (t + 1)(↓))。先把常数掰出去,现在问题其实就是要找一个(t)满足(t+d[i] / (t+1)(↓))最小,结论是当(t)取到(sqrt(d[i]))附近时最小,这个精确的结论可以参考题解,如果只是需要推出这个结论:因为下取整函数是不增的,合并一下函数取值可以发现他大概是一个倒钩的形状,会在某个位置降到最小,然后求出这个分界点即可(注意由于下取整函数是阶梯式的取值,所以三分法求峰是会失效的)。可以大概的把这个分界点求出是在根号附近的,那么枚举(t = [max(0,sqrt(d[i]) - 5),sqrt(d[i]) + 5])找出最小的答案即可。那么更新的时候有两种情况:(dist[u] < t),此时在原地等待到(t)时刻即可;(dist[u] geq t)应该立即转移,因为再等会让值变大。其他的部分和最短路一样。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<ll,int> pli;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
#define x first
#define y second
const int N = 1e5+7,M = 2 * N;
int edge[M],succ[M],D[M],C[M],ver[N],idx;
ll dist[N];
bool st[N];
void add(int u,int v,int c,int d)
{
edge[idx] = v;
C[idx] = c;
D[idx] = d;
succ[idx] = ver[u];
ver[u] = idx++;
}
void dijkstra()
{
forn(i,1,N - 1) dist[i] = 1e18;dist[1] = 0;
priority_queue<pli,vector<pli>,greater<pli>> pq;pq.push({0,1});
while(!pq.empty())
{
auto _ = pq.top();pq.pop();
ll d = _.x;int u = _.y;
if(st[u]) continue;
st[u] = 1;
for(int i = ver[u];~i;i = succ[i])
{
int v = edge[i],sq = sqrt(D[i]);
forn(target,max(0,sq - 10),sq + 10)
{
ll t = max(d,1ll*target);
if(dist[v] > t + C[i] + (D[i] / (t + 1)))
{
dist[v] = t + C[i] + (D[i] / (t + 1));
pq.push({dist[v],v});
}
}
}
}
}
int main()
{
memset(ver,-1,sizeof ver);
int n,m;scanf("%d%d",&n,&m);
while(m--)
{
int u,v,c,d;scanf("%d%d%d%d",&u,&v,&c,&d);
add(u,v,c,d);add(v,u,c,d);
}
dijkstra();
printf("%lld
",dist[n] == 1e18 ? -1 : dist[n]);
return 0;
}
F - Hanjo 2
数据范围挺大的,首先考虑做一个常规的dp
再做优化:
- 状态:(f[i][S])表示当前处理到第(i)列,状态为(S)的方案数
- 入口:(f[1][0] = 1)其余为(0)
- 出口:(f[m][0])
转移比较特殊,通过样例可以发现某些状态可以被一个状态更新多次(01
可以被00
这个状态转移两次,因为1x1
的块某些时候可以替代1x2
大小的块),可以把转移方程写成这样:(f[i + 1][T] = sumlimits_{S}C(T,S)*f[i][S]),其中系数(C(T,S))是只与状态(T,S)有关的常数,实际含义是状态(S)转移到状态(T)时产生的贡献的系数。
-
计算 (C(T,S)):枚举每个状态(S),枚举将(S)填满的所有方式,并且维护下一列的状态(T),填满(S)后维护系数即可。
-
加速递推过程:看到数据不难想到应该是让(i)维的转移降低到(O(log(m))),这部分可以通过矩阵快速幂加速:构造一个转移矩阵
trans
,满(trans * f[i] = f[i + 1])。那么(trans^m * f[0] = f[m])。而(trans)矩阵恰好就是(C(T,S))的定义,于是就可以使用矩阵快速幂加速了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)
const int N = 6,M = 1 << N,MOD = 998244353;
int cnt[N][M],n;
ll m;
struct Mat
{
int c[M][M];
Mat()
{
memset(c,0,sizeof c);
}
int* operator[](int x)
{
return c[x];
}
friend Mat operator*(Mat& a,Mat& b)
{
Mat res;
forn(i,0,M - 1) forn(j,0,M - 1) forn(k,0,M - 1) res[i][j] = (res[i][j] + 1ll * a[i][k] * b[k][j] % MOD) % MOD;
return res;
}
friend Mat qpow(Mat& a,ll b)
{
Mat res;forn(i,0,M - 1) res[i][i] = 1;
while(b)
{
if(b & 1) res = res * a;
a = a * a;
b >>= 1;
}
return res;
}
}trans,f;
void dfs(int from,int cur,int nxt)
{
if(cur == (1 << n) - 1) ++trans[from][nxt];
else
{
forn(i,0,n - 1)
{
if(cur >> i & 1) continue;
dfs(from,cur | (1 << i),nxt);
if(i < n - 1 && !(cur >> (i + 1) & 1)) dfs(from,cur | (1 << i) | (1 << (i + 1)),nxt);
dfs(from,cur | (1 << i),nxt | (1 << i));
break;
}
}
}
int main()
{
scanf("%d%lld",&n,&m);
forn(S,0,(1 << n) - 1) dfs(S,S,0);
forn(S,0,(1 << n) - 1)
{
forn(T,0,(1 << n ) - 1)
cout << trans[S][T] << " ";
cout << endl;
}
f[0][0] = 1;
trans = qpow(trans,m);
f = trans * f;
printf("%d
",f[0][0]);
return 0;
}