这次成功靠队友 AK 了,罚时 rk2,orz 队友们!
套路题,首先考虑怎么算某个字符串 s s s 第一次出现的期望步数。考虑 PGF,定义 F ( x ) F(x) F(x) 为第 i i i 步时恰好结束的方案, G ( x ) G(x) G(x) 为第 i i i 步时还未结束的方案。则列出方程:
F + G = x G + 1 G ⋅ x n 2 6 n = ∑ i ∈ b o r d e r ( s ) F ⋅ x n − i 2 6 n − i F+G=xG+1 \\ G \cdot \frac{x^n}{26^n}=\sum_{i \in border(s)}F \cdot \frac{x^{n-i}}{26^{n-i}} F+G=xG+1G⋅26nxn=i∈border(s)∑F⋅26n−ixn−i
下面那个算式相当于强制加上一个 s s s 使其结束,但是它有可能提前结束,右边就是提前结束的所有方案。
注意到我们要求 F ′ ( 1 ) F'(1) F′(1),并且由第一个式子, F ′ ( 1 ) = G ( 1 ) F'(1)=G(1) F′(1)=G(1),第二个式子带入 x = 1 x=1 x=1,可以得到:
G ( 1 ) = ∑ i ∈ b o r d e r ( s ) 2 6 i G(1)=\sum_{i \in border(s)} 26^i G(1)=i∈border(s)∑26i
于是这题就好做了,不难发现回文串的所有 border 都是回文串,建出回文树,变成两条到根的链比大小。直接倍增哈希即可,复杂度 O ( n log n ) O(n \log n) O(nlogn)。
PAM 写错了挂了三发,自闭了……快读板子略去
const int MAXN = 100005, B = 233, MOD0 = 1e9 + 7, MOD1 = 1e9 + 9;
int T, n, Q;
char str[MAXN];
ULL pw[MAXN];
namespace PAM {
int tot, lst, son[MAXN][26], par[MAXN], len[MAXN], bel[MAXN];
int nxt[20][MAXN], dep[MAXN];
ULL h[20][MAXN];
void init() {
memset(son, 0, sizeof(son));
lst = len[0] = 0, tot = 1;
par[0] = 1, len[1] = -1;
}
void extend(int p) {
int c = str[p] - 'a', q = lst;
while (str[p - 1 - len[q]] != str[p]) q = par[q];
if (!son[q][c]) {
int nq = ++tot, t = par[q];
while (str[p - 1 - len[t]] != str[p]) t = par[t];
par[nq] = son[t][c];
son[q][c] = nq;
len[nq] = len[q] + 2;
}
lst = son[q][c];
bel[p] = lst;
}
void build() {
for (int i = 2; i <= tot; i++) {
nxt[0][i] = par[i];
h[0][i] = len[i];
dep[i] = dep[par[i]] + 1;
}
for (int i = 1; i < 20; i++)
for (int j = 2; j <= tot; j++) {
int f = nxt[i - 1][j], d = min(1 << (i - 1), dep[f]);
nxt[i][j] = nxt[i - 1][f];
h[i][j] = h[i - 1][f] + h[i - 1][j] * pw[d];
}
}
int get(int l, int r) {
int p = bel[r];
for (int i = 19; i >= 0; i--)
if (len[nxt[i][p]] >= r - l + 1) p = nxt[i][p];
return p;
}
int cmp(int p, int q) {
// if (len[p] != len[q]) return len[p] < len[q] ? -1 : 1;
for (int i = 19; i >= 0 && p && q; i--)
if (h[i][p] == h[i][q])
p = nxt[i][p], q = nxt[i][q];
return len[p] == len[q] ? 0 : (len[p] < len[q] ? -1 : 1);
}
}
int main() {
// freopen("input.txt", "r", stdin);
for (int i = pw[0] = 1; i <= 1e5 + 3; i++) {
pw[i] = pw[i - 1] * B;
}
for (IO::read(T); T--;) {
PAM::init();
IO::read(n);
IO::read(str + 1);
for (int i = 1; i <= n; i++)
PAM::extend(i);
// printf("%d\n", PAM::tot - 1);
PAM::build();
IO::read(Q);
while (Q--) {
int a, b, c, d; IO::read(a, b, c, d);
int p = PAM::get(a, b), q = PAM::get(c, d);
int x = PAM::cmp(p, q);
IO::print(x == 0 ? "draw" : (x < 0 ? "sjfnb" : "cslnb"));
}
}
IO::ioflush();
return 0;
}
这个题的难度在于复杂度分析吧(
首先,不难发现最优策略是可以算出来的。我们考虑如果没有 L L L 的限制会怎么样,那么我们必然是先把 t = ( n − 1 ) m o d ( R − 1 ) + 1 t=(n-1) \bmod (R-1)+1 t=(n−1)mod(R−1)+1 个最小的合起来(特别的,如果 t = 1 t=1 t=1 就视为不操作),然后接下来不断的把最小的 R R R 个合起来。
但是现在有了 L L L 的限制,不一定能操作 t t t 了,于是我们的操作序列大致就变成了 L , L , . . . , L , t , R , R , . . . , R L,L,...,L,t,R,R,...,R L,L,...,L,t,R,R,...,R,其中 L ≤ t ≤ R L \le t \le R L≤t≤R。有多少个 L L L, t t t 是多少,有多少个 R R R 都可以预先算出来,接下来的难点就变成了模拟。
我们考虑把数字相同的一起处理,即把所有数字表示成若干个 ( a , b ) (a,b) (a,b) 这样二元组的形式,表示数字 b b b 出现了 a a a 次,然后暴力模拟即可。具体的,我们可以开一个队列,我们发现每次操作之后形成的数字必然递增,因此可以顺序地塞到这个队列里,可以没有 log \log log 地模拟。
关键是,这样做的复杂度是多少呢?首先最坏情况必然是 L = R = 2 L=R=2 L=R=2 时,我们分析这个时候的复杂度。
接下来是博主 yy 的证明,如果有伪证请告知。
考虑定义一个局面(含有 ( a 1 , b 1 ) , . . . , ( a n , b n ) (a_1,b_1),...,(a_n,b_n) (a1,b1),...,(an,bn) 共 n n n 个二元组)的势能函数为 ∑ i = 1 n 1 + log a i \sum_{i=1}^n 1+\log a_i ∑i=1n1+logai,其中 a i a_i ai 是 b i b_i bi 的出现次数。
假设 b 1 b_1 b1 是最小的,那么接下来进行分类讨论:
因此两次操作必然会使势能函数至少减少 1 1 1,复杂度为 O ( ∑ log a i ) O(\sum \log a_i) O(∑logai)。
#include
typedef long long LL;
using namespace std;
const int MAXN = 100005;
struct Data { LL a, b; } que[5000005];
int T, n, he, ta, aa[MAXN]; LL L, R, ans;
void push(LL a, LL b) {
ans += a * b;
if (b <= n) que[b].a += a;
else que[++ta] = Data { a, b };
}
void calc(LL cnt, LL len) {
while (cnt > 0) {
LL &a = que[he].a, b = que[he].b, na = min(a / len, cnt);
if (na) {
push(na, b * len), cnt -= na;
a -= na * len;
if (!a) { ++he; continue; }
}
if (cnt == 0) break;
--cnt;
LL c = 0, s = 0;
for (; he <= ta; ++he) {
c += que[he].a;
s += que[he].a * que[he].b;
if (c >= len) break;
}
assert(c >= len);
push(1, s - (c - len) * que[he].b);
if (c == len) ++he;
else que[he].a = c - len;
}
}
int main() {
for (scanf("%d", &T); T--;) {
scanf("%d%lld%lld", &n, &L, &R);
LL sum = 0;
for (int i = 1; i <= n; i++) {
scanf("%d", aa + i);
que[i] = Data { aa[i], i };
sum += aa[i];
}
he = 1, ta = n, ans = 0;
LL cL = 0, cR = (sum - 1) / (R - 1), t = (sum - 1) % (R - 1) + 1;
if (t > 1) {
cL = R == L ? 1e18 : (R - t - 1) / (R - L) + 1;
if (cL > cR) { puts("-1"); continue; }
cR -= cL - 1;
t = sum - cR * (R - 1) - (cL - 1) * (L - 1);
assert(t >= L && t <= R);
}
if (cL) {
calc(cL - 1, L);
calc(1, t);
}
calc(cR, R);
assert(he == ta && que[he].a == 1);
printf("%lld\n", ans);
}
return 0;
}
中档题,我们考虑如果没有非祖先关系的限制怎么做,显然对于每个颜色维护一个长度为 20 20 20 的数组表示当前位为 0 0 0 的有多少个,直接计算贡献即可。
那么接下来也很明了了,考虑减掉祖先关系的贡献。于是对于每种颜色维护两棵动态开点线段树,每个点上是个长度为 20 20 20 的数组,意义同上。线段树维护 dfs 序,两棵线段树分别表示祖先对子节点的贡献(需要支持区间加,单点查询),以及子节点对祖先的贡献(单点修改,区间查询)。直接跑一遍即可。
由于这题可以离线,所以可以把所有操作离线下来对于每种颜色进行计算,这样可以把动态开点线段树换成 bit,可能常数才能通过。复杂度 O ( 20 n log n ) O(20n \log n) O(20nlogn)。
code by djq
#include
#define rep(i, n) for(int i = 0; i < (int)(n); i ++)
#define rep1(i, n) for(int i = 1; i <= (int)(n); i ++)
#define MP make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int MOD = 998244353;
int n, q, col[100005], val[100005];
vector<int> G[100005];
int qt[100005], qv[100005], qx[100005];
vector<pair<int, PII> > hvq[100005];
LL ans[100005];
int dfn[100005], dfo[100005], tot;
void dfs(int v, int par)
{
dfn[v] = tot ++;
rep(i, G[v].size()) {
int u = G[v][i];
if(u == par) continue;
dfs(u, v);
}
dfo[v] = tot - 1;
}
struct fwt
{
int dat[100005][21];
void add(int x, int d, int coef)
{
for(; x <= n; x += x & -x) {
rep(i, 20) dat[x][i] += coef * (d >> i & 1);
dat[x][20] += coef;
}
}
LL query(int x, int d)
{
LL ret = 0;
for(; x > 0; x -= x & -x)
rep(i, 20) ret += 1LL * (d >> i & 1 ? dat[x][20] - dat[x][i] : dat[x][i]) << i;
return ret;
}
}d0, d1;
int cnt[21];
LL modify(int v, int d)
{
int dir = 1;
if(d < 0) {
d = ~d; dir = -1;
}
d0.add(dfn[v] + 1, d, dir);
d0.add(dfo[v] + 2, d, -dir);
d1.add(dfn[v] + 1, d, dir);
LL ret = -dir * (d0.query(dfn[v] + 1, d) + d1.query(dfo[v] + 1, d) - d1.query(dfn[v], d));
rep(i, 20) cnt[i] += dir * (d >> i & 1);
cnt[20] += dir;
rep(i, 20) ret += 1LL * dir * (d >> i & 1 ? cnt[20] - cnt[i] : cnt[i]) << i;
return ret;
}
void solve()
{
scanf("%d", &n);
rep1(i, n) scanf("%d", &col[i]);
rep1(i, n) scanf("%d", &val[i]);
rep1(i, n) G[i].clear();
rep(i, n - 1) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
scanf("%d", &q);
rep1(i, n) hvq[i].clear();
rep1(i, n) hvq[col[i]].push_back(MP(0, MP(i, val[i])));
rep(i, q) {
scanf("%d%d%d", &qt[i], &qv[i], &qx[i]);
if(qt[i] == 2) {
hvq[col[qv[i]]].push_back(MP(i + 1, MP(qv[i], ~val[qv[i]])));
col[qv[i]] = qx[i];
hvq[col[qv[i]]].push_back(MP(i + 1, MP(qv[i], val[qv[i]])));
} else {
hvq[col[qv[i]]].push_back(MP(i + 1, MP(qv[i], ~val[qv[i]])));
val[qv[i]] = qx[i];
hvq[col[qv[i]]].push_back(MP(i + 1, MP(qv[i], val[qv[i]])));
}
}
rep1(i, n) hvq[col[i]].push_back(MP(q + 1, MP(i, ~val[i])));
tot = 0;
dfs(1, -1);
rep(i, q + 1) ans[i] = 0;
rep1(i, n) {
rep(j, hvq[i].size())
ans[hvq[i][j].first] += modify(hvq[i][j].second.first, hvq[i][j].second.second);
}
rep(i, 21) assert(cnt[i] == 0);
rep1(i, q) ans[i] += ans[i - 1];
rep(i, q + 1) printf("%lld\n", ans[i]);
}
int main()
{
int T;
scanf("%d", &T);
while(T --) solve();
return 0;
}
签到题,对于每个 i i i 算一下最大的 j j j 使得 a j + . . . + a i a_j+...+a_i aj+...+ai 为 p p p 的倍数,直接 dp 即可。复杂度 O ( n ) O(n) O(n)。
code by csl,同样略去快读
const int maxn = 100111;
int n, p, a[maxn], mx[maxn], dp[maxn];
void solve()
{
memset(mx, -1, sizeof(mx));
get2(n, p);
for(int i=1; i<=n; i++) get1(a[i]);
dp[0] = 0;
mx[0] = 0;
int sum = 0;
for(int i=1; i<=n; i++)
{
sum = (sum + a[i]) % p;
dp[i] = std::max(dp[i-1], mx[sum] + 1);
mx[sum] = std::max(mx[sum], dp[i]);
}
printendl(*std::max_element(dp+1, dp+n+1));
}
int main()
{
int tc;
get1(tc);
while(tc--) solve();
return 0;
}
签到题,并查集维护当前集合中 p o w e r = 1 , 2 power=1,2 power=1,2 的个数,然后每次合并的时候让答案减掉:从两个合并的集合中各抽取一个点,剩下的集合中抽取一个点形成一个合法队伍的方案数。
#include
typedef long long LL;
using namespace std;
const int MAXN = 100005, MOD = 1e9 + 7;
LL cnt1, cnt2;
struct Node {
int p; LL a, b;
LL merge(const Node &d) {
LL x = a, y = b;
a += d.a;
b += d.b;
return (y * d.b + y * d.a + x * d.b) * (cnt2 - b) + y * d.b * (cnt1 - a);
}
} nd[MAXN];
int T, n;
LL ans;
int find(int x) {
return x == nd[x].p ? x : nd[x].p = find(nd[x].p);
}
void merge(int x, int y) {
x = find(x), y = find(y);
assert(x != y);
nd[y].p = x;
ans -= nd[x].merge(nd[y]);
}
int main() {
for (scanf("%d", &T); T--;) {
scanf("%d", &n);
cnt1 = cnt2 = 0;
for (int i = 1; i <= n; i++) {
int t; scanf("%d", &t);
++(t == 1 ? cnt1 : cnt2);
nd[i] = Node { i, t == 1, t == 2 };
}
ans = cnt2 * (cnt2 - 1) * (cnt2 - 2) / 6 + cnt2 * (cnt2 - 1) * cnt1 / 2;
printf("%lld\n", ans % MOD);
for (int i = 1; i < n; i++) {
int u, v; scanf("%d%d", &u, &v);
merge(u, v);
printf("%lld\n", ans % MOD);
}
}
return 0;
}
我们考虑算 [ 1 , r ] [1,r] [1,r] 的答案,枚举一段和 r r r 相同的前缀,以及接下来不同的那一位,接下来枚举 d d d 的出现次数,于是对于每种数字在后面的出现次数都已经固定了。直接 dp, f ( i , j ) f(i,j) f(i,j) 表示当前 dp 到第 i i i 种被限制的数字,还剩 j j j 个空位的方案。于是复杂度大概是 1 0 2 × 1 8 4 10^2 \times 18^4 102×184,大概有个 0.1 0.1 0.1 左右的常数,但是还是过不了。
考虑不枚举不同的那一位,在 dp 状态上加一位 0 / 1 0/1 0/1 即 f ( i , j , 0 / 1 ) f(i,j,0/1) f(i,j,0/1) 表示当前状态下,第一个不同的位置有没有填的方案数。这样就可以去掉一个 10 10 10。
注意我们还没有考虑含有前导 0 0 0 的情况,这种情况直接预处理出 [ 1 , 1 0 i ) [1,10^i) [1,10i) 的答案就好。
一次询问大概要进行 1 0 5 10^5 105 级别的运算,还是大概能通过的。
code by lqs,卡了一些常数
#include
using namespace std;
int test,d,arr[22],cnt,num[22],lst,la,nw,lt[22];
long long l,r,dp[2][2][22],jc[22],cur1,cur2,dd[22][22][22],DP[2][22],cur,cur3;
long long injc(long long f,int x)
{
for (int i=2;i<=x;i++) f/=i;
return f;
}
long long solve(long long x,int d)
{
if (!x) return 0;
cnt=0;
while(x)
{
arr[++cnt]=x%10;
x/=10;
}
reverse(arr+1,arr+cnt+1);
long long ans=0;
for (int i=1;i<=cnt;i++)
{
memset(num,0,sizeof(num));
for (int k=1;k<i;k++) num[arr[k]]++;
lst=cnt-i+1;
for (int k=num[d];k<=num[d]+lst;k++)
{
la=0;nw=1;memset(dp,0,sizeof(dp));
dp[0][0][0]=jc[lst-1];
bool fg=0;
for (int h=0;h<=9;h++)
{
if (h==d) lt[h]=k-num[d];
else
{
lt[h]=k-num[h];
if (lt[h]<0)
{
fg=1;
break;
}
}
}
if (fg) continue;
for (int h=0;h<=9;h++)
{
memset(dp[nw],0,sizeof(dp[nw]));
if (h==d)
{
for (int p=0;p+lt[h]<=lst;p++)
{
dp[nw][0][p+lt[h]]+=injc(dp[la][0][p],lt[h]);
dp[nw][1][p+lt[h]]+=injc(dp[la][1][p],lt[h]);
if (h<arr[i] && lt[h] && (i!=1 || h)) dp[nw][1][p+lt[h]]+=injc(dp[la][0][p],lt[h]-1);
}
}
else
{
for (int p=0;p<=lst;p++)
{
cur1=cur3=dp[la][0][p];cur2=dp[la][1][p];
if (!cur1 && !cur2) continue;
for (int r=0;r+p<=lst && r<lt[h];r++)
{
dp[nw][0][r+p]+=cur1;
dp[nw][1][r+p]+=cur2;
if (h<arr[i] && r && (i!=1 || h)) dp[nw][1][r+p]+=cur3;
cur3=cur1;
cur1/=(r+1);cur2/=(r+1);
}
}
}
la^=1;nw^=1;
}
ans+=dp[la][1][lst];
}
}
for (int i=1;i<cnt;i++)
{
for (int j=1;j<=9;j++)
{
// if (dd[cnt-1-i][j][d]) cout<
ans+=dd[cnt-i-1][j][d];
}
}
memset(num,0,sizeof(num));
for (int i=1;i<=cnt;i++) num[arr[i]]++;
bool flg=0;
for (int i=0;i<=9;i++)
{
if (i!=d && num[i]>=num[d]) flg=1;
}
if (!flg) ans++;
return ans;
}
void Init()
{
jc[0]=1;
for (int i=1;i<=20;i++) jc[i]=jc[i-1]*i;
for (int rd=0;rd<=9;rd++)
{
for (int i=0;i<=18;i++)
{
lst=i;
for (int j=1;j<=9;j++)
{
memset(num,0,sizeof(num));
num[j]++;
for (int k=num[rd];k<=num[rd]+lst;k++)
{
la=0;nw=1;memset(DP,0,sizeof(DP));
DP[0][0]=jc[lst];
for (int h=0;h<=9;h++)
{
memset(DP[nw],0,sizeof(DP[nw]));
for (int p=0;p<=lst;p++)
{
if (!DP[la][p]) continue;
cur=DP[la][p];
for (int r=0;r+p<=lst;r++)
{
if (h==rd && r+num[rd]!=k)
{
cur/=(r+1);
continue;
}
if (h!=rd && r+num[h]>=k) break;
DP[nw][r+p]+=cur;
cur/=(r+1);
}
}
la^=1;nw^=1;
}
dd[lst][j][rd]+=DP[la][lst];
}
}
}
}
}
int main()
{
// freopen("cin.in","r",stdin);
// freopen("cout.out","w",stdout);
scanf("%d",&test);
Init();
while(test--)
{
scanf("%lld%lld%d",&l,&r,&d);
printf("%lld\n",solve(r,d)-solve(l-1,d));
}
return 0;
}
爆搜题,我们考虑肯定有一条删掉的边位于当前 1 1 1 到 n n n 的最短路上。由于权值随机,最短路长度不会很长,直接暴力跑出来枚举删掉哪一条,再继续爆搜第二条删掉的边即可。复杂度 O ( O( O(松 ) ) )。
code by djq
#include
#define rep(i, n) for(int i = 0; i < (int)(n); i ++)
#define rep1(i, n) for(int i = 1; i <= (int)(n); i ++)
#define MP make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
int n, k, dis[55][55];
int pre[55], dat[55];
bool vis[55];
int gen_sp()
{
rep1(i, n) {
dat[i] = INF;
pre[i] = -1;
vis[i] = false;
}
priority_queue<PII> que;
dat[1] = 0;
que.push(MP(-dat[1], 1));
while(!que.empty()) {
int v = que.top().second;
que.pop();
if(vis[v]) continue;
vis[v] = true;
if(v == n) return dat[v];
rep1(i, n) if(dat[i] > dat[v] + dis[v][i]) {
dat[i] = dat[v] + dis[v][i];
pre[i] = v;
que.push(MP(-dat[i], i));
}
}
return dat[n];
}
int dfs(int cnt)
{
int ret = gen_sp();
if(cnt == k) return ret;
vector<PII> hv;
for(int i = n; i != 1; i = pre[i]) hv.push_back(MP(i, pre[i]));
ret = 0;
rep(i, hv.size()) {
int u = hv[i].first, v = hv[i].second, w = dis[u][v];
dis[u][v] = dis[v][u] = INF;
ret = max(ret, dfs(cnt + 1));
dis[u][v] = dis[v][u] = w;
}
return ret;
}
void solve()
{
scanf("%d%d", &n, &k);
rep(i, n * (n - 1) / 2) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
dis[u][v] = dis[v][u] = w;
}
printf("%d\n", dfs(0));
}
int main()
{
int T;
scanf("%d", &T);
while(T --) solve();
return 0;
}
简单题吧,就是精度问题有点坑爹。注意在 hdu 上千万不要用 long double,一定要用 double!
在平面上画出一个等边三角形网格图,二分时间,就变成了求线段和网格的交点个数。
显然网格只有三种直线,相互独立,每种直线都是平行的,交点数非常好算。复杂度 O ( log ) O(\log) O(log)。
code by csl
typedef double db;
const db sqrt3 = sqrt(0.75);
int len, x, y, vx, vy, k;
db lenX;
db x1, y1, vx1, vy1;
db x2, y2, vx2, vy2;
db x3, y3, vx3, vy3;
int check(db l, db r)
{
if(l > r) std::swap(l, r);
if(r - l >= 10000000.0 * lenX) return 10000000;
int vl = ceil(l / lenX), vr = floor(r / lenX);
if(vl > vr) return 0;
return vr - vl + 1;
}
int check(db M) { return check(y1, y1 + vy1 * M) + check(y2, y2 + vy2 * M) + check(y3, y3 + vy3 * M); }
void solve()
{
get3(len, x, y); get3(vx, vy, k);
lenX = len * sqrt3;
x1 = x - len * 0.5;
y1 = y;
vx1 = vx;
vy1 = vy;
vx2 = -vx1 / 2 - vy1 * sqrt3;
vy2 = -vy1 / 2 + vx1 * sqrt3 ;
x2 = -x1 / 2 - y1 * sqrt3;
y2 = -y1 / 2 + x1 * sqrt3;
vx3 = -vx1 / 2 + vy1 * sqrt3;
vy3 = -vy1 / 2 - vx1 * sqrt3;
x3 = -x1 / 2 + y1 * sqrt3;
y3 = -y1 / 2 - x1 * sqrt3;
db L = 0, R = 1e11, M;
for(int i=1; i<=80; i++)
{
M = (L + R) / 2;
if(check(M) < k) L = M;
else R = M;
}
printf("%.10lf\n", L);
}
int main()
{
int tc;
get1(tc);
while(tc--) solve();
return 0;
}
好像是个签到题,没看过题,先咕这。
首先考虑一个很简单的 n 2 n^2 n2 dp: f ( i , j ) f(i,j) f(i,j) 表示当前一个手指在 i i i,另一个手指在 j j j( i > j i>j i>j)时的最小代价。如果 j < i − 1 j < i-1 j<i−1,那么从 f ( i − 1 , j ) f(i-1,j) f(i−1,j) 转移来;否则从 f ( i − 1 , k ) f(i-1,k) f(i−1,k), k < i − 1 k < i-1 k<i−1 都可以转移过来。
于是我们发现,特殊的只有 f ( i , i − 1 ) f(i,i-1) f(i,i−1) 这个值,记 g ( i ) = f ( i , i − 1 ) g(i)=f(i,i-1) g(i)=f(i,i−1) ,新的 f ( i ) = min { f ( i , j ) ∣ j < i } f(i)=\min\{f(i,j)|jf(i)=min{f(i,j)∣j<i}。于是可以写出如下转移方程( d ( i , j ) d(i,j) d(i,j) 表示从 i i i 到 j j j 的代价):
g ( i ) = min { g ( j ) + d ( j − 1 , i ) + d ( j , j + 1 ) + . . + d ( i − 2 , i − 1 ) ∣ j < i } f ( i ) = min { g ( i ) , f ( i − 1 ) + d ( i − 1 , i ) } g(i)=\min\{g(j)+d(j-1,i)+d(j,j+1)+..+d(i-2,i-1)|jg(i)=min{g(j)+d(j−1,i)+d(j,j+1)+..+d(i−2,i−1)∣j<i}f(i)=min{g(i),f(i−1)+d(i−1,i)}
g g g 的转移前缀和优化一下,然后讨论 4 4 4 种情况(绝对值正负号)把 d ( j − 1 , i ) d(j-1,i) d(j−1,i) 拆开,就是四个二维数点。于是 cdq 分治即可,答案即为 f ( n ) f(n) f(n)。复杂度 O ( n log 2 n ) O(n \log^2 n) O(nlog2n)。
code by csl
const int maxn = 300111;
struct BIT
{
LL bit[maxn];
int mark[maxn], timer;
BIT()
{
memset(mark, 0, sizeof(mark));
timer = 0;
}
void modify(int x, LL v)
{
for(; x<maxn; x+=x&-x)
{
if(mark[x] != timer)
{
mark[x] = timer;
bit[x] = v;
}
else bit[x] = std::min(bit[x], v);
}
}
LL query(int x)
{
LL ret = Linf;
for(; x; x-=x&-x) if(mark[x] == timer) ret = std::min(ret, bit[x]);
return ret;
}
}B1, B2;
int n, x[maxn], y[maxn], idx[maxn], idy[maxn];
LL pre[maxn], dp[maxn];
void solve(int l, int r)
{
if(l == r) return;
if(l > r) return;
int mid = (l + r) / 2;
solve(l, mid);
std::vector<pii> vs;
for(int i=l; i<=r; i++)
{
if(i <= mid) vs.pb(mp(x[i], i));
else vs.pb(mp(x[i+1], i));
}
std::sort(vs.begin(), vs.end());
B1.timer++;
B2.timer++;
for(int t=0; t<(int)vs.size(); t++)
{
int i = vs[t].ss;
if(i <= mid)
{
B1.modify(idy[i], dp[i] - x[i] - y[i] - pre[i+1]);
B2.modify(n - idy[i] + 1, dp[i] - x[i] + y[i] - pre[i+1]);
}
else
{
dp[i] = std::min(dp[i], B1.query(idy[i+1]) + x[i+1] + y[i+1] + pre[i]);
dp[i] = std::min(dp[i], B2.query(n - idy[i+1] + 1) + x[i+1] - y[i+1] + pre[i]);
}
}
B1.timer++;
B2.timer++;
std::reverse(vs.begin(), vs.end());
for(int t=0; t<(int)vs.size(); t++)
{
int i = vs[t].ss;
if(i <= mid)
{
B1.modify(idy[i], dp[i] + x[i] - y[i] - pre[i+1]);
B2.modify(n - idy[i] + 1, dp[i] + x[i] + y[i] - pre[i+1]);
}
else
{
dp[i] = std::min(dp[i], B1.query(idy[i+1]) - x[i+1] + y[i+1] + pre[i]);
dp[i] = std::min(dp[i], B2.query(n - idy[i+1] + 1) - x[i+1] - y[i+1] + pre[i]);
}
}
solve(mid+1, r);
}
void solve()
{
get1(n);
for(int i=1; i<=n; i++)
{
x[i] = rand();
y[i] = rand();
get2(x[i], y[i]);
}
if(n <= 2)
{
puts("0");
return;
}
std::vector<int> vx(x+1, x+n+1), vy(y+1, y+n+1);
std::sort(vx.begin(), vx.end()); vx.erase(std::unique(vx.begin(), vx.end()), vx.end());
std::sort(vy.begin(), vy.end()); vy.erase(std::unique(vy.begin(), vy.end()), vy.end());
for(int i=1; i<=n; i++) idx[i] = std::upper_bound(vx.begin(), vx.end(), x[i]) - vx.begin();
for(int i=1; i<=n; i++) idy[i] = std::upper_bound(vy.begin(), vy.end(), y[i]) - vy.begin();
pre[1] = 0;
for(int i=2; i<=n; i++) pre[i] = pre[i-1] + abs(x[i] - x[i-1]) + abs(y[i] - y[i-1]);
for(int i=1; i<n; i++) dp[i] = pre[i];
solve(1, n-1);
LL ans = Linf;
for(int i=1; i<n; i++) ans = std::min(ans, pre[n] - pre[i+1] + dp[i]);
printendl(ans);
}
int main()
{
int tc;
get1(tc);
while(tc--) solve();
return 0;
}
事实上这题不是非常难吧,需要推一些式子。
我们考虑稍微修改一下原问题,即每次不删掉当前元素,只是打个标记。问第 c c c 个元素是第 i i i 个被打上标记的元素的概率。这个问题显然与原问题等价,于是我们可以枚举编号小于当前元素、编号大于当前元素的有多少个在当前元素之前就被打上了标记。然后列出关于答案的生成函数( i i i 是枚举它在第几圈被打上的标记):
∑ i = 0 ∞ p ( 1 − p ) i ∑ j = 0 c − 1 ( c − 1 j ) x j ( 1 − ( 1 − p ) i + 1 ) j ( 1 − p ) ( i + 1 ) ( c − 1 − j ) ∑ k = 0 n − c ( n − c k ) x k ( 1 − ( 1 − p ) i ) k ( 1 − p ) i ( n − c − k ) \sum_{i=0}^\infty p(1-p)^i \sum_{j=0}^{c-1}\binom{c-1}{j}x^j(1-(1-p)^{i+1})^j(1-p)^{(i+1)(c-1-j)}\sum_{k=0}^{n-c}\binom{n-c}{k}x^k(1-(1-p)^i)^k(1-p)^{i(n-c-k)} i=0∑∞p(1−p)ij=0∑c−1(jc−1)xj(1−(1−p)i+1)j(1−p)(i+1)(c−1−j)k=0∑n−c(kn−c)xk(1−(1−p)i)k(1−p)i(n−c−k)
= ∑ i = 0 ∞ p ( 1 − p ) i ( ( 1 − ( 1 − p ) i + 1 ) x + ( 1 − p ) i + 1 ) c − 1 ( ( 1 − ( 1 − p ) i ) x + ( 1 − p ) i ) n − c =\sum_{i=0}^\infty p(1-p)^i((1-(1-p)^{i+1})x+(1-p)^{i+1})^{c-1}((1-(1-p)^i)x+(1-p)^i)^{n-c} =i=0∑∞p(1−p)i((1−(1−p)i+1)x+(1−p)i+1)c−1((1−(1−p)i)x+(1−p)i)n−c
为了便于交换求和顺序,我们改写成这样:
= ∑ i = 0 ∞ p ( 1 − p ) i ( x + ( 1 − p ) i + 1 ( 1 − x ) ) c − 1 ( x + ( 1 − p ) i ( 1 − x ) ) n − c =\sum_{i=0}^\infty p(1-p)^i(x+(1-p)^{i+1}(1-x))^{c-1}(x+(1-p)^i(1-x))^{n-c} =i=0∑∞p(1−p)i(x+(1−p)i+1(1−x))c−1(x+(1−p)i(1−x))n−c
= ∑ i = 0 ∞ p ( 1 − p ) i ∑ j = 0 c − 1 ∑ k = 0 n − c x n − 1 − j − k ( 1 − p ) i ( j + k ) + j ( 1 − x ) j + k ( c − 1 j ) ( n − c k ) =\sum_{i=0}^\infty p(1-p)^i\sum_{j=0}^{c-1}\sum_{k=0}^{n-c}x^{n-1-j-k}(1-p)^{i(j+k)+j}(1-x)^{j+k}\binom{c-1}{j}\binom{n-c}{k} =i=0∑∞p(1−p)ij=0∑c−1k=0∑n−cxn−1−j−k(1−p)i(j+k)+j(1−x)j+k(jc−1)(kn−c)
= p ∑ j = 0 c − 1 ∑ k = 0 n − c x n − 1 − j − k ( 1 − p ) j ( 1 − x ) j + k ( c − 1 j ) ( n − c k ) ∑ i = 0 ∞ ( 1 − p ) i ( j + k + 1 ) =p\sum_{j=0}^{c-1}\sum_{k=0}^{n-c}x^{n-1-j-k}(1-p)^j(1-x)^{j+k}\binom{c-1}{j}\binom{n-c}{k}\sum_{i=0}^\infty (1-p)^{i(j+k+1)} =pj=0∑c−1k=0∑n−cxn−1−j−k(1−p)j(1−x)j+k(jc−1)(kn−c)i=0∑∞(1−p)i(j+k+1)
很多关于 j + k j+k j+k 的项提示我们枚举 j + k j+k j+k 的值。
= p ∑ t = 0 n − 1 x n − 1 − t ( 1 − x ) t 1 1 − ( 1 − p ) t + 1 ∑ j ≥ 0 ( c − 1 j ) ( n − c t − j ) ( 1 − p ) j =p\sum_{t=0}^{n-1}x^{n-1-t}(1-x)^t\frac{1}{1-(1-p)^{t+1}}\sum_{j \ge 0}\binom{c-1}{j}\binom{n-c}{t-j}(1-p)^j =pt=0∑n−1xn−1−t(1−x)t1−(1−p)t+11j≥0∑(jc−1)(t−jn−c)(1−p)j
= p x n − 1 ∑ t = 0 n − 1 ( x − 1 − 1 ) t 1 1 − ( 1 − p ) t + 1 ∑ j ≥ 0 ( c − 1 j ) ( n − c t − j ) ( 1 − p ) j =px^{n-1}\sum_{t=0}^{n-1}(x^{-1}-1)^t\frac{1}{1-(1-p)^{t+1}}\sum_{j \ge 0}\binom{c-1}{j}\binom{n-c}{t-j}(1-p)^j =pxn−1t=0∑n−1(x−1−1)t1−(1−p)t+11j≥0∑(jc−1)(t−jn−c)(1−p)j
后面的那个求和,是只和 t t t 有关的,是个卷积的形式,记 g t g_t gt 表示 ( x − 1 − 1 ) t (x^{-1}-1)^t (x−1−1)t 的系数(即后面那个东西),记:
G ( x ) = ∑ t = 0 n − 1 x t g t G(x)=\sum_{t=0}^{n-1}x^tg_t G(x)=t=0∑n−1xtgt
则,我们需要求出 G ( x − 1 ) G(x-1) G(x−1) 之后 reverse 一下系数即可。复杂度 O ( n log n ) O(n \log n) O(nlogn)。
代码就不放 IO 板子和 NTT 板子了。
int A[MAXN], B[MAXN], C[MAXN], T, n;
LL fac[MAXN], inv[MAXN], pw[MAXN];
void init() {
int n = 1e6;
for (int i = fac[0] = 1; i <= n; i++)
fac[i] = fac[i - 1] * i % MOD;
inv[n] = modpow(fac[n], MOD - 2);
for (int i = n; i > 0; i--)
inv[i - 1] = inv[i] * i % MOD;
}
int main() {
init();
for (IO::read(T); T--;) {
int a, b, c;
IO::read(n, a, b, c);
LL p = (LL)a * modpow(b, MOD - 2) % MOD;
for (int i = pw[0] = 1; i <= n; i++)
pw[i] = pw[i - 1] * (MOD + 1 - p) % MOD;
for (int i = 0; i < c; i++)
A[i] = pw[i] * fac[c - 1] % MOD * inv[i] % MOD * inv[c - 1- i] % MOD;
for (int i = 0; i <= n - c; i++)
B[i] = fac[n - c] * inv[i] % MOD * inv[n - c - i] % MOD;
NTT::get_mul(A, B, C, c, n - c + 1, n);
for (int i = 0; i < n; i++)
C[i] = (LL)C[i] * modpow(MOD + 1 - pw[i + 1], MOD - 2) % MOD;
for (int i = 0; i < n; i++) {
A[i] = i & 1 ? MOD - inv[i] : inv[i];
B[i] = C[i] * fac[i] % MOD;
}
reverse(B, B + n);
NTT::get_mul(A, B, A, n, n, n);
reverse(A, A + n);
for (int i = 0; i < n; i++)
A[i] = A[i] * inv[i] % MOD;
reverse(A, A + n);
for (int i = 0; i < n; i++)
IO::print(A[i] * p % MOD);
}
IO::ioflush();
return 0;
}