期望得分:(100+100+100=300pts)
实际得分:(100+100+0=200pts)
恭喜我自己,没有算空间挂了 100pts。
7:40 发题面
7:48 读完题,发现 T1 和二进制有关
7:56 调完 T1。T2 期望感觉巨难直接开 T3,想了想 T3 的做法稍微算了一下复杂度,感觉正确性没什么问题,复杂度可能有点卡,然后开写。
8:45 调了好久终于过了样例,然后一发过了大样例,只不过跑的有点慢,算了一下发现复杂度果然过不去,对一些地方进行了优化,极限数据本地跑了 7s,应该刚好能卡过去的程度。可能会爆 long long 就把 define int long long
写上了。(埋下伏笔)
9:10 上厕所,回来开始想 T2,推了推感觉 k=1 的情况挺好写,就把这一部分写了,现在对于正解还是没有思路的。
9:35 想起来答应给 @斜揽残箫 写的题解还没写,于是先把他的题解写完了。
10:00 有上了次厕所,回来看了看 T2,转化了一下思路,感觉可以做,手推了式子开始写。
10:35 终于调出来了,有惊无险(一发过大样例还是挺不错的)。检查了一下,把这个题解写了,然后开始摆烂,拿出了 BS 给的小说。
10:55 收卷。果然自己被卡空间了,wsdsb。/hanx
感觉 T2 最后能想出来非常的幸运,不过 T3 没算空间就比较傻逼了。
为什么我老挂分啊/ll,为什么我老挂分啊/ll,为什么我老挂分啊/ll,为什么我老挂分啊/ll,为什么我老挂分啊/ll,为什么我老挂分啊/ll,为什么我老挂分啊/ll,为什么我老挂分啊/ll
T3 最后在评测机和洛谷上测了测,有两个点是踩着 2000ms 的线卡过去的。
T1 | T2 | T3 |
---|---|---|
T208866 city | T208867 paint | T208869 vanilla |
T1 city
如果把数转化成二进制再来看这三个操作就非常简单了。
分别是左移一位+1,左移一位,右移一位。
所以每次操作只能更改两个数的低位,那我们看看这两个数从高位开始匹配能匹配多少位,答案就是两个数剩下的不能匹配的位数和。
int main()
{
// freopen("city.in","r",stdin);
// freopen("city.out","w",stdout);
n = read();
while(n--) {
x = read(), y = read(), sc = 0, top = 0;
while(x) a[++sc] = (x & 1), x >>= 1;
while(y) b[++top] = (y & 1), y >>= 1;
while(sc && top && a[sc] == b[top]) sc--, top--;
printf("%d
", sc + top);
}
fclose(stdin);
fclose(stdout);
return 0;
}
T2 paint
如果你按照题意去模拟会非常难搞,考虑转化一下思路。
首先,一张画纸期望是什么颜色只与它被修改几次有关。因此我们可以设 (f_{i,j}) 表示一张画纸被修改了 (j) 次变成了 (i) 的期望。
然后我们考虑一下区间覆盖的问题,如果一次作画框住了区间 ([l,r])。设这个区间长度为 (len),我们考虑其中的某个位置会被选中多少次,那就是假设它被选中其他的位置随便选的方案数,所以它会被选中 (2^{len-1}),而整个区间的子集有 (2^{len}) 种,说的可能麻烦了,但不难发现,一个位置被选中的概率为 (frac{1}{2})。
所以呢,我们就可以利用差分快速统计出每个位置被区间覆盖了多少次。
然后开始统计答案!
我们枚举每个位置,枚举它可能被覆盖了几次,计算出这个次数,计算它期望的颜色,对所有的期望的颜色求和就是答案。
那写成公式就是:
其中 (i) 表示第 (i) 个位置,(k) 表示枚举到被覆盖了 (k) 次,(d) 表示一共被 (d) 个区间覆盖,(j) 表示期望为颜色 (j) 的概率。
时间复杂度为 (mathcal O(Kc^2 + nK(K+c)))
/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
Believe yourself !
OHHHHHHHHHHHHHHHHHHH一发过大样例!
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
#define LD long double
#define orz cout<<"lkp AK IOI!"<<endl
const int MAXN = 10005;
using namespace std;
const int INF = 1e9+7;
const int mod = 1e9+7;
int n, c, K;
LD ans = 0.0;
int L[MAXN], R[MAXN];
int cnt[MAXN];
LD f[200][200];
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
namespace Subtask1 {
void Solve() {
int len = R[1] - L[1] + 1, lst = n - len;
printf("%.3lf
", lst + 1.0 * len / 2 + 1.0 * len * (c - 1) / 4);
return ;
}
}
void Init() {
f[1][0] = 1.0;
for(int j = 0; j <= K; ++j) {
for(int i = 0; i < c; ++i) {
for(int k = 0; k < c; ++k) {
f[i * k % c][j + 1] += f[i][j] / c;
}
}
}
}
int main()
{
// freopen("paint.in","r",stdin);
// freopen("paint.out","w",stdout);
n = read(), c = read(), K = read();
Init();
for(int i = 1; i <= K; ++i) L[i] = read(), R[i] = read();
// if(K == 1) return Subtask1::Solve(), 0;
for(int i = 1; i <= K; ++i) cnt[L[i]]++, cnt[R[i] + 1] --;
for(int i = 1; i <= n; ++i) cnt[i] = cnt[i - 1] + cnt[i];
for(int i = 1; i <= n; ++i) {
LD p = 1.0;
for(int j = 1; j <= cnt[i]; ++j) p = 1.0 * p * j / 2;
for(int j = 0; j <= cnt[i]; ++j) {
LD res = p, x = 0;
for(int k = 0; k < c; ++k) x = x + f[k][j] * k;
for(int k = 1; k <= j; ++k) res = 1.0 * res / k;
for(int k = 1; k <= cnt[i] - j; ++k) res = 1.0 * res / k;
res *= x;
// cout<<res<<"
";
ans = ans + res;
}
}
printf("%.3Lf", ans);
return 0;
}
T3 vanilla
你看这个数据范围就知道要状压,你看它要求最短路径就得状压最短路。
我们先跑个 Folyd 求出两两之间的最短距离。
我们在设两个数组 (f[S][i], g[S][i]) 分别表示以 (1) 作为出发点,已经进行收割/播种的状态为 (S),最后一个点为 (i) 的最短距离,(g) 数组则表示以 (n) 为出发点。
上面这个 (f,g) 数组可以预处理出来。时间复杂度为 (mathcal O(2^n n^2)),这个复杂度有点危,我们发现我们不用枚举全部的,我们只需要枚举 (S) 中 (1) 的个数 (le frac{n-2}{2}+2) 的状态即可。复杂度就被我们优化掉一个 (n)(我们本质还是枚举所有子集,只不过在某些子集只做了 (mathcal O(n)) Check )
然后你枚举首先选择哪 (frac{n-2}{2}) 个花田收割,设枚举的收割的花田的状态为 (S)。
我们考虑收割的过程,考虑怎么把前一半的最短路和后一半的最短路合并起来,那就是,枚举在集合中的点 (i) 和不在集合中的点 (j),设答案为 (res),则:
其中 (M) 表示所有点的全集。
这样就保证了 (S) 状态中选中的点一定会在前 (frac{n-2}{2}) 花田里收割,剩余的点在后来收割。
同理,我们考虑播种的过程,其实就是反过来,我们再设一个播种的答案 (ans),则
最终答案就是 (min {res + ans})。
这个直接上搜索就行,控制一下搜索上限,可以控制复杂度为 (mathcal O(2^{frac{n-2}{2}} frac{n-2}{2}^{2}))。
在本地跑了 7s,但 XP 的拉是众所周知的,所以应该是没问题的。
除非正解有一个更牛逼的做法。
/*
Work by: Suzt_ilymtics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
const int MAXN = 2e6+50;
const int INF = 0x3f3f3f3f3f3f3f3fll;
const int mod = 1e9+7;
int n, m, N, M, ans = INF;
int dis[22][22];
int f[MAXN][22], g[MAXN][22];
int a[22], sc = 0;
int b[22], top = 0;
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
void dfs(int S, int pos, int cnt) {
if(cnt > N) return ;
if(pos == n) {
if(cnt != N) return ;
int res1 = INF, res2 = INF;
// cout<<S<<"
";
for(int i = 1; i <= sc; ++i) {
for(int j = 1; j <= top; ++j) {
int x = a[i], y = b[j];
res1 = min(res1, f[S | 1][x] + g[M ^ (S | 1)][y] + dis[x][y]);
res2 = min(res2, g[S | (1 << n - 1)][x] + f[M ^ (S | (1 << n - 1))][y] + dis[x][y]);
}
}
// cout<<res1<<" "<<res2<<"
";
ans = min(ans, res1 + res2);
return ;
}
a[++sc] = pos;
dfs(S | (1 << pos - 1), pos + 1, cnt + 1);
--sc;
b[++top] = pos;
dfs(S, pos + 1, cnt);
--top;
}
signed main()
{
// freopen("vanilla.in","r",stdin);
// freopen("vanilla.out","w",stdout);
n = read(), m = read();
if(n <= 2) return puts("0"), 0;
N = (n - 2) / 2, M = (1 << n) - 1;
memset(dis, 0x3f, sizeof dis);
for(int i = 1; i <= n; ++i) dis[i][i] = 0;
for(int i = 1, u, v, w; i <= m; ++i) {
u = read() + 1, v = read() + 1, w = read();
dis[u][v] = dis[v][u] = min(dis[u][v], w);
}
for(int k = 1; k <= n; ++k) {
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= n; ++j) {
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
if(n == 3) {
cout<<1ll * (dis[1][2] + dis[2][3]) * 2<<"
";
return 0;
}
memset(f, 0x3f, sizeof f);
f[1][1] = 0;
for(int S = 0; S < (1 << n); ++S) {
int cnt = 0;
for(int i = 1; i <= n; ++i) if(S & (1 << i - 1)) cnt++;
if(cnt > N + 2) continue;
for(int i = 1; i <= n; ++i) {
if(!(S & (1 << i - 1))) continue;
for(int j = 1; j <= n; ++j) {
if(S & (1 << j - 1)) continue;
f[S | (1 << j - 1)][j] = min(f[S | (1 << j - 1)][j], f[S][i] + dis[i][j]);
}
}
}
memset(g, 0x3f, sizeof g);
g[(1 << n - 1)][n] = 0;
for(int S = 0; S < (1 << n); ++S) {
int cnt = 0;
for(int i = 1; i <= n; ++i) if(S & (1 << i - 1)) cnt++;
if(cnt > N + 2) continue;
for(int i = 1; i <= n; ++i) {
if(!(S & (1 << i - 1))) continue;
for(int j = 1; j <= n; ++j) {
if(S & (1 << j - 1)) continue;
g[S | (1 << j - 1)][j] = min(g[S | (1 << j - 1)][j], g[S][i] + dis[i][j]);
}
// cout<<S<<" "<<i<<" "<<g[S][i]<<"
";
}
}
dfs(0, 2, 0);
cout<<ans<<"
";
return 0;
}