2019.10.10模拟赛
T1 马里奥
\(n * m\)的地图,可以由‘#’到达上方的‘#’,代价是高度差,也可以横向到达相邻的‘#’。求最小的代价(不是代价和,是最小的一个代价)。
解1:建图,二分最小的代价,跑最短路。时间复杂度\(O(n^2log^2(n))\)
inline int calc(const int &x, const int &y) { return (x - 1) * m + y; }
int st,ed;
int d[MAXN * MAXN];
bool v[MAXN * MAXN];
inline void spfa(const int mid)
{
memset(d, 0x3f, sizeof(d));
memset(v, 0, sizeof(v));
queue<int> q;
q.push(st);
d[st] = 0;
v[st] = 1;
while(q.size())
{
register int x = q.front();
q.pop();
v[x] = 0;
for(register int i = head[x]; i; i = e[i].nxt)
{
register int y = e[i].ver, z = e[i].edge;
if(z > mid)
continue;
if(d[y] > d[x] + z)
{
d[y] = d[x] + z;
if(!v[y])
q.push(y),v[y] = true;
}
}
}
}
inline int find()
{
register int l = 0,r = n, mid, res = 0;
while(l <= r)
{
mid = (l + r) >> 1;
spfa(mid);
if(d[ed] == 0x3f3f3f3f)
l = mid + 1;
else
res = mid,r = mid - 1;
}
return res;
}
int main()
{
scanf("%d %d",&n, &m);
for(register int i = 1; i <= n; ++i)
scanf("%s",s[i] + 1);
scanf("%d %d",&x, &y);
if(x == n)
{
puts("0");
fclose(stdin);
fclose(stdout);
return 0;
}
st = calc(n,1);
ed = calc(x,y);
for(register int i = n; i; --i)
{
for(register int j = 1; j < m; ++j)
{
if(s[i][j] == '#' && s[i][j+1] == '#')
{
register int x = calc(i,j);
add(x, x+1, 0);
add(x+1, x, 0);
}
}
for(register int j = 1; j <= m; ++j)
{
if(s[i][j] == '#')
for(register int k = i - 1; k;--k)
{
if(s[k][j] == '#')
{
register int x = calc(i, j);
register int y = calc(k, j);
register int z = i - k;
add(x, y, z);
add(y, x, z);
break;
}
}
}
}
register int ans = find();
printf("%d\n",ans);
return 0;
}
解2:使用并查集维连通性,贪心的取全局最小边。类似\(kruskal\)最小生成树算法。时间复杂度\(O(n^2 log^2(n))\)
实际因为这个算法常数小,比二分+最短路快不少。
inline int calc(const int &x, const int &y) { return (x - 1) * m + y; }
struct node
{
int x, y, edge;
inline bool operator<(const node &t) const { return edge < t.edge; }
} e[MAXN * MAXN << 1];
int tot;
int st, ed;
int fa[MAXN * MAXN];
inline int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
int main()
{
scanf("%d %d", &n, &m);
for (register int i = 1; i <= n; ++i)
scanf("%s", s[i] + 1);
scanf("%d %d", &x, &y);
if (x == n)
{
puts("0");
fclose(stdin);
fclose(stdout);
return 0;
}
st = calc(n, 1);
ed = calc(x, y);
for (register int i = n; i; --i)
{
for (register int j = 1; j < m; ++j)
{
if (s[i][j] == '#' && s[i][j + 1] == '#')
{
register int x = calc(i, j);
e[++tot].x = x;
e[tot].y = x + 1;
e[tot].edge = 0;
}
}
for (register int j = 1; j <= m; ++j)
{
if (s[i][j] == '#')
for (register int k = i - 1; k; --k)
{
if (s[k][j] == '#')
{
register int x = calc(i, j);
register int y = calc(k, j);
register int z = i - k;
e[++tot].x = x;
e[tot].y = y;
e[tot].edge = z;
break;
}
}
}
}
for (register int i = 1; i <= n * n; ++i)
fa[i] = i;
sort(e + 1, e + tot + 1);
register int ans = n * 2;
for (register int i = 1; i <= tot; ++i)
{
int x = e[i].x, y = e[i].y, z = e[i].edge;
x = find(x);
y = find(y);
if (x == y)
continue;
fa[x] = y;
if (find(st) == find(ed))
{
ans = z;
break;
}
}
printf("%d\n", ans);
}
T2 祭司
\(i\)个变量,每个变量有取值范围,将这些变量分成两组,使两组变量之和的差的最大值最小,求这个最小值。
正解:我们设分组为\(A,B\),\(sum1\)为该组最小值之和,\(sum2\)为该组最大值之和,\(sum\)为该组所有值之和。
易得最大值 = \(max(sum2(A) - sum1(B),sum1(A) - sum2(B)) = max(sum(A) - sum1(A + B),sum(A) - sum2(A+B) )\)
\(sum1(A + B),sum2(A+B)\)都是常量,答案只与\(sum(A)\)有关。
使用可行性DP,求出所有可能的\(sum(A)\),统计最优解即可。
可以使用bitset优化。
int main()
{
poread(n);
for (register short i = 1; i <= n; ++i)
poread(lim[i].l), poread(lim[i].r);
for (register short i = 1; i <= n; ++i)
sum1 += lim[i].l, sum2 += lim[i].r;
f[0] = true;
for (register short i = 1; i <= n; ++i)
f |= (f << (lim[i].l + lim[i].r));
register unsigned short i = (sum2 + sum1) >> 1;
for (; i >= 0; --i)
if (f[i])
break;
printf("%d", sum2 - i);
}
解2:模拟退火(数据太水甚至不用调参)
double eps = 1e-5, delta = 0.993;
struct node
{
short l, r;
} lim[MAXN];
bool tag[MAXN];
int n, m;
inline unsigned short calc()
{
unsigned short res1 = 0, res2 = 0;
unsigned short sum1 = 0, sum2 = 0;
for (register short i = 1; i <= n; ++i)
(tag[i]) ? (sum1 += lim[i].l) : (sum2 += lim[i].r);
res1 = abs(sum2 - sum1);
sum2 = sum1 = 0;
for (register short i = 1; i <= n; ++i)
(tag[i]) ? (sum1 += lim[i].r) : (sum2 += lim[i].l);
res2 = abs(sum2 - sum1);
return max(res1, res2);
}
int ans = INT_MAX;
int ans_fire;
inline void sa()
{
ans_fire = INT_MAX;
double T = 1000;
for (register int i = 1; i <= n; ++i)
tag[i] = rand() & 1;
while (T > eps)
{
T *= delta;
register int x = rand() % n + 1, y = rand() % n + 1;
if(x == y)
continue;
swap(tag[x], tag[y]);
int now = calc();
if(now < ans_fire || rand() % 1000 < T)
ans_fire = now;
else
swap(tag[x], tag[y]);
}
}
int main()
{
srand(time(0));
poread(n);
for (register int i = 1; i <= n; ++i)
poread(lim[i].l), poread(lim[i].r);
for (register int i = 1; i <= 200; ++i)
{
sa();
ans = min(ans_fire, ans);
}
cout << ans << endl;
}
解3:直接\(rand()\)在哪个分组,真的能过√。
理论上来说\(random\_shuffle\)能过的但是前几个点不太行。
for (register short i = 1; i <= 5000; ++i)
{
for (register short j = 1; j <= n; ++j)
whe[j] = mt() & 1;
ans = min(ans, calc());
}
T3 AK(真就AK啊)
给你一个数字序列,每次查询一段区间的数字和,并且把它们都变成原来的平方。答案对\(2305843008676823040\)取模。
大佬们通过打表发现某个数平方几次之后就不动了。。。
所以可以直接线段树维护这段区间。
官方证明:
数据不强,貌似30次之后就是定值了。
由于模数很大会爆long long,需要龟速乘。
__int128表示不服
inline long long spow(long long a, long long b)
{
register long long ans = 0;
for (; b; b >>= 1)
{
if (b & 1)
{
ans = ans + a;
if (ans > MOD)
ans -= MOD;
}
a = (a << 1);
if (a > MOD)
a -= MOD;
}
return ans;
}
const int MAXN = 65537;
class T
{
#define ls (tr << 1)
#define rs (tr << 1 | 1)
public:
struct node
{
long long sum;
bool tag;
node() {}
node(const long long &_sum, const unsigned char &_tag)
{
sum = _sum;
tag = _tag;
}
friend inline node operator+(const node &a, const node &b)
{
long long sum = a.sum + b.sum;
if (sum > MOD)
sum -= MOD;
return node(sum, a.tag & b.tag);
}
} tree[MAXN << 2];
inline void build(int tr, int L, int R)
{
if (L == R)
{
poread(tree[tr].sum);
return;
}
register int mid = (L + R) >> 1;
build(ls, L, mid);
build(rs, mid + 1, R);
// tree[tr] = tree[ls] + tree[rs];
tree[tr].sum = tree[ls].sum + tree[rs].sum;
if (tree[tr].sum > MOD)
tree[tr].sum -= MOD;
tree[tr].tag = tree[ls].tag & tree[rs].tag;
}
inline void change(int tr, int L, int R, int l, int r)
{
if (tree[tr].tag)
return;
if (L == R)
{
register long long tmp = tree[tr].sum;
tree[tr].sum = spow(tree[tr].sum, tree[tr].sum);
if (tmp == tree[tr].sum)
tree[tr].tag = 1;
return;
}
register int mid = (L + R) >> 1;
if (r <= mid)
change(ls, L, mid, l, r);
else if (l > mid)
change(rs, mid + 1, R, l, r);
else
change(ls, L, mid, l, mid), change(rs, mid + 1, R, mid + 1, r);
// tree[tr] = tree[ls] + tree[rs];
tree[tr].sum = tree[ls].sum + tree[rs].sum;
if (tree[tr].sum > MOD)
tree[tr].sum -= MOD;
tree[tr].tag = tree[ls].tag & tree[rs].tag;
}
inline long long query(int tr, int L, int R, int l, int r)
{
if (L == l && R == r)
return tree[tr].sum;
register int mid = (L + R) >> 1;
if (r <= mid)
return query(ls, L, mid, l, r);
else if (l > mid)
return query(rs, mid + 1, R, l, r);
else
{
long long res = query(ls, L, mid, l, mid) + query(rs, mid + 1, R, mid + 1, r);
if (res > MOD)
res -= MOD;
return res;
}
}
#undef ls
#undef rs
} t;
int n, m;
int main()
{
poread(n);
poread(m);
t.build(1, 1, n);
for (register int i = 1, l, r; i <= m; ++i)
{
poread(l), poread(r);
fdata::out(t.query(1, 1, n, l, r));
putchar('\n');
t.change(1, 1, n, l, r);
}
}
如果用__int128可以跑得很快(毕竟少常数)
const __int128 YU = 1ll;
inline void change(int tr, int L, int R, int l, int r) {
if (tree[tr].tag)
return;
if (L == R) {
register unsigned long long tmp = tree[tr].sum;
tree[tr].sum = YU * tree[tr].sum * tree[tr].sum % MOD;
if (tmp == tree[tr].sum)
tree[tr].tag = 1;
return;
}
register int mid = (L + R) >> 1;
if (r <= mid)
change(ls, L, mid, l, r);
else if (l > mid)
change(rs, mid + 1, R, l, r);
else
change(ls, L, mid, l, mid), change(rs, mid + 1, R, mid + 1, r);
// tree[tr] = tree[ls] + tree[rs];
tree[tr].sum = tree[ls].sum + tree[rs].sum;
if (tree[tr].sum > MOD)
tree[tr].sum -= MOD;
tree[tr].tag = tree[ls].tag & tree[rs].tag;
}
ps:在HOJ开始了卡常大战