HDU 2020 多校第三场 游记

这次成功靠队友 AK 了,罚时 rk2,orz 队友们!

1001

套路题,首先考虑怎么算某个字符串 s s 第一次出现的期望步数。考虑 PGF,定义 F ( x ) F(x) 为第 i i 步时恰好结束的方案, G ( x ) G(x) 为第 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}}

下面那个算式相当于强制加上一个 s s 使其结束,但是它有可能提前结束,右边就是提前结束的所有方案。

注意到我们要求 F ( 1 ) F'(1) ,并且由第一个式子, F ( 1 ) = G ( 1 ) F'(1)=G(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

于是这题就好做了,不难发现回文串的所有 border 都是回文串,建出回文树,变成两条到根的链比大小。直接倍增哈希即可,复杂度 O ( n log n ) O(n \log n)

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;
}

1002

这个题的难度在于复杂度分析吧(

首先,不难发现最优策略是可以算出来的。我们考虑如果没有 L L 的限制会怎么样,那么我们必然是先把 t = ( n 1 ) m o d ( R 1 ) + 1 t=(n-1) \bmod (R-1)+1 个最小的合起来(特别的,如果 t = 1 t=1 就视为不操作),然后接下来不断的把最小的 R R 个合起来。

但是现在有了 L L 的限制,不一定能操作 t t 了,于是我们的操作序列大致就变成了 L , L , . . . , L , t , R , R , . . . , R L,L,...,L,t,R,R,...,R ,其中 L t R L \le t \le R 。有多少个 L L t t 是多少,有多少个 R R 都可以预先算出来,接下来的难点就变成了模拟。

我们考虑把数字相同的一起处理,即把所有数字表示成若干个 ( a , b ) (a,b) 这样二元组的形式,表示数字 b b 出现了 a a 次,然后暴力模拟即可。具体的,我们可以开一个队列,我们发现每次操作之后形成的数字必然递增,因此可以顺序地塞到这个队列里,可以没有 log \log 地模拟。

关键是,这样做的复杂度是多少呢?首先最坏情况必然是 L = R = 2 L=R=2 时,我们分析这个时候的复杂度。

接下来是博主 yy 的证明,如果有伪证请告知。

考虑定义一个局面(含有 ( a 1 , b 1 ) , . . . , ( a n , b n ) (a_1,b_1),...,(a_n,b_n) n n 个二元组)的势能函数为 i = 1 n 1 + log a i \sum_{i=1}^n 1+\log a_i ,其中 a i a_i b i b_i 的出现次数。

假设 b 1 b_1 是最小的,那么接下来进行分类讨论:

  1. a 1 > 1 a_1 > 1 ,则经过这次操作之后势能函数至少减少 1 1
  2. a 1 = 1 a_1 = 1 ,我们考虑 a 2 a_2 的情况。如果 a 2 = 1 a_2 = 1 a 2 = 2 a_2 = 2 ,那么这次操作使得势能函数减少 1 1 ;否则会发现,这次操作即使不减少势能函数,那么下次一定会减少。

因此两次操作必然会使势能函数至少减少 1 1 ,复杂度为 O ( log a i ) O(\sum \log a_i)

#include <bits/stdc++.h>
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;
}

1003

中档题,我们考虑如果没有非祖先关系的限制怎么做,显然对于每个颜色维护一个长度为 20 20 的数组表示当前位为 0 0 的有多少个,直接计算贡献即可。

那么接下来也很明了了,考虑减掉祖先关系的贡献。于是对于每种颜色维护两棵动态开点线段树,每个点上是个长度为 20 20 的数组,意义同上。线段树维护 dfs 序,两棵线段树分别表示祖先对子节点的贡献(需要支持区间加,单点查询),以及子节点对祖先的贡献(单点修改,区间查询)。直接跑一遍即可。

由于这题可以离线,所以可以把所有操作离线下来对于每种颜色进行计算,这样可以把动态开点线段树换成 bit,可能常数才能通过。复杂度 O ( 20 n log n ) O(20n \log n)

code by djq

#include <bits/stdc++.h>
#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;
}

1004

签到题,对于每个 i i 算一下最大的 j j 使得 a j + . . . + a i a_j+...+a_i p p 的倍数,直接 dp 即可。复杂度 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;
}

1005

签到题,并查集维护当前集合中 p o w e r = 1 , 2 power=1,2 的个数,然后每次合并的时候让答案减掉:从两个合并的集合中各抽取一个点,剩下的集合中抽取一个点形成一个合法队伍的方案数。

#include <bits/stdc++.h>
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;
}

1006

我们考虑算 [ 1 , r ] [1,r] 的答案,枚举一段和 r r 相同的前缀,以及接下来不同的那一位,接下来枚举 d d 的出现次数,于是对于每种数字在后面的出现次数都已经固定了。直接 dp, f ( i , j ) f(i,j) 表示当前 dp 到第 i i 种被限制的数字,还剩 j j 个空位的方案。于是复杂度大概是 1 0 2 × 1 8 4 10^2 \times 18^4 ,大概有个 0.1 0.1 左右的常数,但是还是过不了。

考虑不枚举不同的那一位,在 dp 状态上加一位 0 / 1 0/1 f ( i , j , 0 / 1 ) f(i,j,0/1) 表示当前状态下,第一个不同的位置有没有填的方案数。这样就可以去掉一个 10 10

注意我们还没有考虑含有前导 0 0 的情况,这种情况直接预处理出 [ 1 , 1 0 i ) [1,10^i) 的答案就好。

一次询问大概要进行 1 0 5 10^5 级别的运算,还是大概能通过的。

code by lqs,卡了一些常数

#include<bits/stdc++.h>
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<<cnt-i-1<<" "<<j<<" "<<d<<" "<<dd[cnt-1-i][j][d]<<endl;
            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;
}

1007

爆搜题,我们考虑肯定有一条删掉的边位于当前 1 1 n n 的最短路上。由于权值随机,最短路长度不会很长,直接暴力跑出来枚举删掉哪一条,再继续爆搜第二条删掉的边即可。复杂度 O ( O( ) )

code by djq

#include <bits/stdc++.h>
#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;
}

1008

简单题吧,就是精度问题有点坑爹。注意在 hdu 上千万不要用 long double,一定要用 double!

在平面上画出一个等边三角形网格图,二分时间,就变成了求线段和网格的交点个数。

显然网格只有三种直线,相互独立,每种直线都是平行的,交点数非常好算。复杂度 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;
}

1009

好像是个签到题,没看过题,先咕这。

1010

首先考虑一个很简单的 n 2 n^2 dp: f ( i , j ) f(i,j) 表示当前一个手指在 i i ,另一个手指在 j j i > j i>j )时的最小代价。如果 j < i 1 j < i-1 ,那么从 f ( i 1 , j ) f(i-1,j) 转移来;否则从 f ( i 1 , k ) f(i-1,k) k < i 1 k < i-1 都可以转移过来。

于是我们发现,特殊的只有 f ( i , i 1 ) 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)|j<i\} 。于是可以写出如下转移方程( d ( i , j ) d(i,j) 表示从 i i 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)|j<i\} \\ f(i)=\min\{g(i),f(i-1)+d(i-1,i)\}

g g 的转移前缀和优化一下,然后讨论 4 4 种情况(绝对值正负号)把 d ( j 1 , i ) d(j-1,i) 拆开,就是四个二维数点。于是 cdq 分治即可,答案即为 f ( n ) f(n) 。复杂度 O ( n log 2 n ) O(n \log^2 n)

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;
}

1011

事实上这题不是非常难吧,需要推一些式子。

我们考虑稍微修改一下原问题,即每次不删掉当前元素,只是打个标记。问第 c c 个元素是第 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 ) 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 ( 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 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}

= 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)}

很多关于 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

= 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

后面的那个求和,是只和 t t 有关的,是个卷积的形式,记 g t g_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 1 ) G(x-1) 之后 reverse 一下系数即可。复杂度 O ( n log n ) O(n \log n)

代码就不放 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;
}

猜你喜欢

转载自blog.csdn.net/WAautomaton/article/details/107641451
今日推荐