C.Link with Nim Game
题意:
nim游戏的稍微加强版。只不过此时必胜的人想尽快胜,必输的人想尽量慢的输。
问游戏可进行多少轮及先手在第一轮有多少种操纵。
思路:
首先应该知道nim游戏。
整体异或和为0时,先手必输。异或和非0时,先手必胜。
考虑先手必输,由于输的人想尽量慢的输,贪心考虑是否可以拿掉1个,且留给后手的最优策略同样是拿
掉一个。可以发现这种情况一定可以存在。取整体二进制的最后一位1即可,后手只能镜像操纵。那么轮
数就可以确定了,为 ∑ i = 1 n a [ i ] \sum_{i = 1} ^ {n} a[i] ∑i=1na[i]。考虑先手有多少种操纵才能使得先手拿掉1个,后手的最优策略也是拿掉
一个。可以发现只要满足当前lowbit位为1,且当前组内所有此二进制位为1的和这个数相同即可,才能保证
后手最优策略一定是拿掉1个。
X3 2 1 0
A1 1 0 0
B1 1 1 0
C1 1 0 0
D1 1 1 0
假设先手从A拿掉1个,那么后手因为想快点赢,那么完全可以从B拿,使得B变为(1000),此时是B的最优
策略。
考虑先手必胜,由于胜的人想尽量胜,那么最优策略肯定是在满足留给后手为异或为0的局面的情况下,
拿的石头还要尽量多。此时轮到后手,后手也可采取拿掉1个,导致先手也只能拿掉1个才能使得异或值
非0,轮数为 ∑ i = 1 n a [ i ] − m a x ( a [ i ] − a [ i ] x o r x ) + 1 \sum_{i = 1} ^ {n} a[i] - max(a[i] - a[i] \;xor \;x) + 1 ∑i=1na[i]−max(a[i]−a[i]xorx)+1(x为整体异或和),那么第一轮的先手操纵为 $max
(a[i] - a[i] ;xor ;x)$的次数
typedef pair <int, int> PII;
ll a[maxn];
ll lowbit(ll x)
{
return x & -x;
}
int main()
{
int T;
scanf("%d", &T);
while(T --)
{
int n;
scanf("%d", &n);
ll sum = 0, res = 0;
for(int i = 1 ; i <= n ; i ++)
scanf("%lld", &a[i]), res ^= a[i], sum += a[i];
if(res == 0)
{
int cc = 0;
for(int i = 0 ; i < 31 ; i ++)
{
int cur = -1;
bool flog = true;
int cnt = 0;
for(int j = 1 ; j <= n ; j ++)
{
ll t = lowbit(a[j]);
if(__lg(lowbit(t)) == i)
cur = t;
}
if(cur > 0)
{
for(int j = 1 ; j <= n ; j ++)
{
if(a[j] >> i & 1)
{
if(lowbit(a[j]) == cur)
cnt ++;
else
{
flog = false;
break;
}
}
}
if(flog)
cc += cnt;
}
}
printf("%lld %d\n", sum, cc);
}
else // res ! = 0
{
ll maxx = 0;
int ccnt = 0;
for(int i = 1 ; i <= n ; i ++)
{
if(a[i] - (a[i] ^ res) == maxx)
ccnt ++;
else if(a[i] - (a[i] ^ res) > maxx)
{
maxx = a[i] - (a[i] ^ res);
ccnt = 1;
}
}
printf("%lld %d\n", sum - maxx + 1, ccnt);
}
}
return 0;
}
D.Link with Game Glitch
题意:
给定n个点和m条有向边,每k * a[i]个b[i]类物品可以换k * c[i]个d[i]类物品。求最大的w(0 <= w < 1)使得不存在一种交换方式得到无限多的物品。
0
<
=
n
<
=
1
e
3
,
2
<
=
m
<
=
2
e
3
0 <= n <= 1e3, 2 <= m <= 2e3
0<=n<=1e3,2<=m<=2e3
1
<
=
b
[
i
]
,
d
[
i
]
<
=
n
,
b
[
i
]
!
=
d
[
i
]
1 <= b[i], d[i] <= n, b[i] \;!= \;d[i]
1<=b[i],d[i]<=n,b[i]!=d[i]
a
[
i
]
,
c
[
i
]
<
=
1
e
3
a[i],c[i] <= 1e3
a[i],c[i]<=1e3
思路:
赛时一眼二分。 假设w越大,那么得到无限多的物品就越多。
考虑如何check。
赛时思路,利用有向图tarjan缩点,找环,对环中每个点为起点,开始跑dfs,再次回到起点时,比起点
的初始值大,那么就会得到无限多的物品。由于两个物品点之间存在多条边,要对每条边得到的值取
max。只有取到max,才能尽量得到无限多的物品,使得最终二分到的w尽量大,其次考虑到a[i],c[i] <=
1e3,会爆掉longlong,赛时不知道怎么处理。
l
o
g
a
+
l
o
g
b
=
l
o
g
a
∗
b
,
l
o
g
a
−
l
o
g
b
=
l
o
g
a
/
b
loga + logb = log a * b, loga - logb = log a / b
loga+logb=loga∗b,loga−logb=loga/b
可以取log,把乘法改为加法,防止爆掉。
赛时思路过于繁琐。实际上在取log的基础上,跑SPFA,判断是否能松弛n次即可(类似于判负环)。
只不过这里要判正环。以每个点为起点,初始化点的权值为1,取log,变为0。
code:
int h[maxn], ne[maxn], e[maxn], idx, cnt[maxn];
double w[maxn], dis[maxn];
bool book[maxn];int n, m;
void add(int u, int v, double ww)
{
w[idx] = ww;
e[idx] = v;
ne[idx] = h[u];
h[u] = idx ++;
}
bool check(double mid)
{
mid = log(mid);
queue <int> alls;
for(int i = 1 ; i <= n ; i ++)
{
alls.push(i);
book[i] = true;
dis[i] = 0;
cnt[i] = 0;
}
while(!alls.empty())
{
int t = alls.front();
alls.pop();
book[t] = false;
for(int i = h[t] ; i != -1 ; i = ne[i])
{
int j = e[i];
if(dis[j] < dis[t] + w[i] + mid)
{
dis[j] = dis[t] + w[i] + mid;
cnt[j] = cnt[t] + 1;
if(cnt[j] >= n) return false; //产生了INF
if(!book[j])
{
book[j] = true;
alls.push(j);
}
}
}
}
return true;
}
int main()
{
scanf("%d %d", &n, &m);
memset(h, -1, sizeof(h)), idx = 0;
for(int i = 1 ; i <= m ; i ++)
{
double a, c; int b, d;
scanf("%lf %d %lf %d", &a, &b, &c, &d);
add(b, d, log(c / a));
}
double l = 0, r = 1;
while(r - l > 1e-8)
{
double mid = (l + r) / 2;
if(check(mid)) l = mid;
else r = mid;
}
printf("%.8lf\n", l);
return 0;
}
H Take the Elevator
题意:
在一个k层楼,有一部电梯,现在共n个人,想从a[i]到b[i]层,电梯一次最多带m个人。且电梯在下降时,只有到达第一层,才会再次向上。且送完n个人后电梯要回到第一层的最小时间。
1
<
=
n
,
m
<
=
2
e
5
,
1
<
=
k
<
=
1
e
9
1 <= n, m <= 2e5, 1 <= k <= 1e9
1<=n,m<=2e5,1<=k<=1e9
a
[
i
]
!
=
b
[
i
]
且
a
[
i
]
,
b
[
i
]
<
=
k
a[i] != b[i] 且 a[i], b[i] <= k
a[i]!=b[i]且a[i],b[i]<=k
思路:
考虑到数据范围,那么正解时间复杂度为O(n) O(nlogn)。
O(n)貌似实现不了。考虑O(nlogn)
看到最小时间,第一眼二分。
但是发现貌似不能二分。
考虑贪心。
考虑向上的电梯,假设此时存在一个最高楼层为x,那么从1–>x必须安排一趟,且因为要求总时间最小,那么肯定是在用这一次的
代价(x - 1)基础上,尽量上楼层高的m个人。可以先按照y从大到小,且x从到小排序,先选取最高的m个人,此后可以发现电梯最后
一段上一定是这m个人,但是前一段,可以另外再次上电梯,只不过不能和最后一段的m个人发生冲突。要保证此时的y <= m个
人的x的最大值即可,x取max,可使得前一段上电梯的人尽量多,假设当前前一段有人上了电梯,那么其前前段还会存在上电梯
的,但是不能和他们冲突。
用multiset维护上/下楼的区间、优先队列维护此时最后一段m个人的y值即可。不断考虑是否前一段时间是否存在可以上电梯(不
发生的冲突下能上就上)。下楼类似。总的时间复杂度为O(nlogn)
code:
typedef pair <int, int> PII;
struct note{
int x;
int y;
bool operator <(const note &a) const{
if(y != a.y) return y > a.y;
else return x > a.x;
}
};int n, m, k;
multiset <note> up, down;
void cal()
{
priority_queue <int> pq;
int t = up.size();
for(int i = 1 ; i <= min(m, t) ; i ++)
{
pq.push((*up.begin()).x);
up.erase(up.begin());
} //先把m个人拿出来
while(pq.size())
{
int temp = pq.top();
auto it = up.lower_bound({temp, temp});
if(it == up.end())
break; //最大的x都满足不了
else
{
pq.pop();
pq.push((*it).x); //替换掉
up.erase(it);
}
}
while(pq.size()) pq.pop();
t = down.size();
for(int i = 1 ; i <= min(m ,t) ; i ++)
{
pq.push((*down.begin()).x);
down.erase(down.begin());
}
while(pq.size())
{
int temp = pq.top();
auto it = down.lower_bound({temp, temp});
if(it == down.end())
break;
else
{
pq.pop();
pq.push((*it).x);
down.erase(it);
}
}
}
int main()
{
scanf("%d %d %d", &n, &m, &k);
for(int i = 1 ; i <= n ; i ++)
{
int a, b;
scanf("%d %d", &a, &b);
if(a < b)
up.insert({a, b});
else // a > b
down.insert({b, a});
}
ll ans = 0;
while(up.size() || down.size())
{
ll t = 0;
if(up.size()) t = max(t, (*up.begin()).y * 1ll);
if(down.size()) t = max(t, (*down.begin()).y * 1ll);
cal();
ans += (t - 1ll) * 2ll;
}
printf("%lld\n", ans);
return 0;
}
K Link with Bracket Sequence I
题意:
给定一个括号序列a长度为n,且a是合法括号序列b长度为m的子序列。
求有多少种合法的括号序列b。对结果mod(1e9 + 7)
1
<
=
n
,
m
<
=
200
1 <= n, m <= 200
1<=n,m<=200
∑
n
<
=
1
e
3
\sum n <= 1e3
∑n<=1e3
思路:
由于之前没有写过由子序列—>原序列的奇数DP。
赛时一直总以为是排列组合。但是实在是无法去重。
也想过按照数据范围,进行DP。但是也不知道如何去重。
由子序列S构造原序列T问方案数这类问题。
如果在T中确定了T[i],匹配的是S中第p个字符S[p]
且在T中确定了T[j],匹配的是S中第p + 1个字符S[p + 1]。
除了保证T[i] =S[p], T[j] = S[p + 1]外,还要保证,T[i + 1] ~ T[j - 1]所有字符外,都和T[i]不同。才能保证方案不重复。
本质上就是一种排列组合。可按照这种思想进行DP求解附加条件。
f[i][j][k]:新串中前i个和原串中前j个是否匹配过且当前前缀和为k的集合。
code:
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int maxn = 1e3 + 10;
char str[maxn];
int f[3][210][210];
const int mod = 1e9 + 7;
int get_mod1(int a, int b)
{
return (a % mod + b % mod) % mod;
}
int main()
{
int T;
scanf("%d", &T);
while(T --)
{
int n, m;
scanf("%d %d", &n, &m);
scanf("%s", str + 1);
memset(f, 0, sizeof(f));
f[0][0][0] = 1;
for(int i = 1 ; i <= m ; i ++)
{
for(int j = 0 ; j <= n ; j ++)
{
for(int k = 0 ; k <= m / 2; k ++)
{
if(j > 0)
{
f[i & 1][j][k] = 0;
if(str[j] == ')' && k + 1 <= m / 2)
f[i & 1][j][k] = f[i - 1 & 1][j - 1][k + 1];
else if(str[j] == '(' && k - 1 >= 0)
f[i & 1][j][k] = f[i - 1 & 1][j - 1][k - 1];
if(str[j] == '(' && k + 1 <= m / 2)
f[i & 1][j][k] = get_mod1(f[i & 1][j][k], f[i - 1 & 1][j][k + 1]);
else if(str[j] == ')' && k - 1 >= 0)
f[i & 1][j][k] = get_mod1(f[i & 1][j][k], f[i - 1 & 1][j][k - 1]);
}
else
{ // f[1][0][1] =
f[i & 1][j][k] = 0;
if(k - 1 >= 0)
f[i & 1][j][k] = f[i - 1 & 1][j][k - 1];
if(k + 1 <= m / 2)
f[i & 1][j][k] = get_mod1(f[i & 1][j][k], f[i - 1 & 1][j][k + 1]);
}
}
}
}
printf("%d\n", f[m & 1][n][0]);
}
return 0;
}
J Link with Arithmetic Progression
题意:
给定一个数组n,可随机修改a[i]的值,但是每个i,最多修改一次,要求把整个数组变成等差数列。修改后的总代价为
∑
i
=
1
i
=
n
(
a
[
i
]
−
a
[
i
]
′
)
2
\sum_{i = 1} ^ {i = n}(a[i] - a[i]') ^ {2}
∑i=1i=n(a[i]−a[i]′)2。可把a[i]修改为小数。求满足条件的最小代价。
∑
n
<
=
1
e
6
\sum n <= 1e6
∑n<=1e6
−
1
e
9
<
=
a
[
i
]
<
=
1
e
9
-1e9 <= a[i] <= 1e9
−1e9<=a[i]<=1e9
思路:
最开始看到这道题,毫无头绪。
无论是首项还是公差d,都有可能变化。 之后发现是求最小代价和,不需要具体方案数。
可向推式子上想想。题目给出了代价的式子。
a[i]’ = (i - 1) d + a[1] 等差数列,其实等差数列就是一条直线方程。
代入式子 ∑ i = 1 i = n ( a [ i ] − a [ i ] ′ ) 2 \sum_{i = 1} ^ {i = n}(a[i] - a[i]') ^ {2} ∑i=1i=n(a[i]−a[i]′)2
发现无论是假设首项a[1]为定值还是公差d为定值,式子都为一个凹函数(二次函数)。存在唯一的极值点。
那么假设三分首项a1。唯一的变量为d。
对于每一个i:
( a [ i ] − a [ i ] ′ ) 2 (a[i] - a[i]') ^ {2} (a[i]−a[i]′)2
= ( i − 1 ) 2 ∗ d 2 + 2 ( i − 1 ) ( a [ 1 ] − a [ i ] ) d + a [ i ] 2 + 2 a [ i ] a [ 1 ] + a [ 1 ] 2 =(i - 1) ^ {2} * d ^ {2} + 2(i - 1)(a[1] - a[i])d + a[i] ^ {2} + 2a[i]a[1] + a[1] ^ {2} =(i−1)2∗d2+2(i−1)(a[1]−a[i])d+a[i]2+2a[i]a[1]+a[1]2
式子中仅有d为变量。为标准的二次函数。且开头向上。
假设首项a[1]固定,对于每一个i都有一个二次函数,且每个函数极值点就是对称轴,但是每个函数极值点都不一样。
考虑如何选取一个公共的极值点。这里的做法是把n条二次函数合成为1条二次函数。 n个二次函数的二
次项系数相加,一次项系数相加。最后再对这条二次函数取对称轴。(具体证明不太会,但是题目那么多数据都能过,也能侧面
证明可行吧)
code:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <cmath>
using namespace std;
//#define int long long
#define NO {puts("NO") ; return ;}
#define YES {puts("YES") ; return ;}
typedef pair<int, int> PII;
const int N = 2e5 + 10 , INF = 0x3f3f3f3f;
int n, m;
int a[N];
namespace GTI {
char gc(void) {
const int S = 1 << 16;
static char buf[S], *s = buf, *t = buf;
if (s == t) t = buf + fread(s = buf, 1, S, stdin);
if (s == t) return EOF;
return *s++;
}
int gti(void) {
int a = 0, b = 1, c = gc();
for (; !isdigit(c); c = gc()) b ^= (c == '-');
for (; isdigit(c); c = gc()) a = a * 10 + c - '0';
return b ? a : -a;
}
}
using GTI::gti;
double calc(double a1)
{
double p = 0, q = 0;
for (int i = 1; i <= n; i ++)
{ // 代入公式
p += 1.0 * (i - 1) * (i - 1);
q += 2.0 * (a1 - a[i]) * (i - 1);
}
double d = -q / (2 * p); // 下凸二次函数的极值点(对称轴)
double res = 0;
for (int i = 1; i <= n; i++)
res += (a[i] - a1 - 1.0 * (i - 1) * d) * (a[i] - a1 - 1.0 * (i - 1) * d);
return res;
}
void solve()
{
n = gti();
for(int i = 1 ; i <= n ; i ++ ) a[i] = gti();
double l = -2e10 , r = 2e10;
while(r - l > 1e-5)
{
double len = r - l;
double mid_l = l + len / 3, mid_r = r - len / 3;
if(calc(mid_l) >= calc(mid_r)) l = mid_l;
else r = mid_r;
}
printf("%.20lf\n", calc(r));
}
signed main()
{
ios::sync_with_stdio(0),cin.tie(0);
int T = 1;
T = gti();
while(T -- ) solve();
return 0;
}
L Link with Level Editor I
题意:
给定n张图,编号为1–n,每张图上有m个点,每个图上各有l[i]条边。每次操纵,要么在当前点要么原地不动直接进入下一张图,要么从当前点走向相邻的的节点,然后进入下一张图。要求选出最少的连续的子串使得能从1号点到达m号点。
n
<
=
1
e
4
,
2
<
=
m
<
=
2
e
3
n <= 1e4, 2 <= m <= 2e3
n<=1e4,2<=m<=2e3
∑
l
<
=
1
e
6
\sum l <= 1e6
∑l<=1e6
思路:
最开始一直以为是图上跑DP,但是无法建拓扑序。。
实际上可以发现,每次都是以1号点为起点,要么在原地不动,要么从1号点走向相邻节点,更新相邻节
点,然后进入下一张图,只有从1号点更新到的点才是有效点,每个去更新有效点的dp值,已经被算出来
了。按照题目数据进行dp,已经满足了拓扑序。
f[i][j]:在第i张图上到达j号点的起点所属图的集合。
属性:max
时间复杂度看似是 n * m + n * l,铁过不去。
但是实际上发现 ∑ l < = 1 e 6 \sum l <= 1e6 ∑l<=1e6,所以时间复杂度为O(n * m + l)
初始化f[i][1] = i
原地不动:
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
j
]
)
原地不动: f[i][j] = max(f[i - 1][j])
原地不动:f[i][j]=max(f[i−1][j])
走到相邻的点
:
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
−
1
]
[
k
]
)
(
k
−
−
−
>
j
)
走到相邻的点: f[i][j] = max(f[i - 1][k]) (k---> j)
走到相邻的点:f[i][j]=max(f[i−1][k])(k−−−>j)
code:
const int maxn = 1e5 + 10;
typedef pair <int, int> PII;
int f[3][3010];
int main()
{
int n, m;
scanf("%d %d", &n, &m);
for(int i = 0 ; i <= 1 ; i ++)
{
for(int j = 0 ; j <= m ; j ++)
f[i][j] = -INF;
}
f[0][1] = 0;
int res = INF;
for(int i = 1 ; i <= n ; i ++)
{
int l;
scanf("%d", &l);
for(int j = 1 ; j <= m ; j ++)
{
if(j == 1) f[i & 1][j] = i;
else f[i & 1][j] = -INF;
f[i & 1][j] = max(f[i & 1][j], f[i - 1 & 1][j]);
}
for(int j = 1 ; j <= l ; j ++)
{
int u, v;
scanf("%d %d", &u, &v);
f[i & 1][v] = max(f[i & 1][v], f[i - 1 & 1][u]);
}
if(f[i & 1][m] != -INF)
{
res = min(res, i - f[i & 1][m]);
}
}
if(res == INF) printf("-1\n");
else printf("%d\n",res);
return 0;
}