【2019 HDU多校集训第二场】

总结:这场多校题目并不难,大概有7到8个可做题。也有几个是基本的套路,有几个需要思考一下。

1.Just Skip The Problem。答案n!。阶乘取模预处理即可。不再贴代码了。

2.Keen On Everything But Triangle。区间第k大问题,主席树。但是这里需要说明一点原因,假设我们发现区间前三大不能组成三角形,那么说明区间第一大一定是不能选的,这样一直递推下去即可。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll inf = 1e9 + 10;


const int maxn = 2e5 + 5;
int ls[maxn * 30], rs[maxn * 30], siz[maxn * 30], tot, rt[maxn], n, m, a[maxn];//update用了几次,就要乘以多少

void ini() { tot = 0; }

void update(int pre, int& u, int l, int r, int pos, int val) {//把u按照pre复制,然后更新pos
    u = ++tot;
    ls[u] = ls[pre]; rs[u] = rs[pre];
    siz[u] = siz[pre] + val;
    if (l == r)return;
    int mid = (l + r) >> 1;
    if (pos <= mid) update(ls[pre], ls[u], l, mid, pos, val);
    else update(rs[pre], rs[u], mid + 1, r, pos, val);
}

int query(int x, int y, int l, int r, int k) 
{
    //查询(y树-x树)差值的区间第k小
    if (l == r)return l;
    int s = siz[ls[y]] - siz[ls[x]];
    int mid = (l + r) >> 1;
    if (k <= s) return query(ls[x], ls[y], l, mid, k);
    else return query(rs[x], rs[y], mid + 1, r, k - s);
}


int main()
{
    while (cin >> n >> m)
    {
        ini();
        for (int i = 1; i <= n; i++) scanf("%d", &a[i]), update(rt[i - 1], rt[i], 1, inf, a[i], 1);
        for (int i = 1; i <= m; i++)
        {
            int l, r;
            scanf("%d%d", &l, &r);
            if (r - l + 1 < 3)
            {
                printf("-1\n");
                continue;
            }
            ll b[3];
            b[1] = query(rt[l - 1], rt[r], 1, inf, r - l + 2 - 1);
            b[2] = query(rt[l - 1], rt[r], 1, inf, r - l + 2 - 2);
            for (int j = 3; j <= r - l + 1; j++)
            {
                b[0] = b[1];
                b[1] = b[2];
                b[2] = query(rt[l - 1], rt[r], 1, inf, r - l + 2 - j);
                if (b[0] >= b[1] + b[2])  continue;
                else 
                {
                    printf("%lld\n", b[0] + b[1] + b[2]);
                    break;
                }
            }
            if (b[0] >= b[1] + b[2]) puts("-1");
        }
    }
}

3.Everything Is Generated In Equal Probability。这个题很多人都是猜出来的????我大写的服气,这也能猜出来,果然是我菜了。其实推导也不难.

 首先,我们假设序列的长度就是n,那么他会有多少个逆序对呢?C(n,2)个。那么,我们在n个数中任意选两个,方案是C(n,2)他们在序列中可以有C(n,2)个位置,然后剩下(n-2)个数随机排列。这样第一步的方案就是:C(n,2)*C(n,2)*(n-2)!.现在考虑这对逆序对的贡献,假设它的贡献是:x.那么首先这对点被选中的概率是 1/4.在下一次递归时,他们被保留地概率是:1/4,如果被保留,就会再一次产生贡献,贡献+1.否则没有贡献,这样就会得到方程:x=(1+x)*1/4+3/4.解出:x=4/3.序列长度为n的期望就是

                                         C(n,2)*C(n,2)*(n-2)!*(4/3)/n!.

最后除全排列,那么他被选中的概率是1/n.所以最后的式子就是:

                                                       \frac{1}{n}*\sum_{i=1}^{n}\frac{(i-2)!*C(i,2)*C(i,2)*\frac{4}{3}}{i!}

可以继续化简,也可以直接写了,因为n很小。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll mod = 998244353;
const  int N = 1e4 + 10;
ll fac[N];
void init()
{
	fac[0] = 1;
	for (int i = 1; i < N; i++)fac[i] = fac[i - 1] * i% mod;
}
ll qpow(ll a, ll b)
{
	ll res = 1;
	while (b)
	{
		if (b & 1)res = res * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}


ll getC(ll i)
{
	ll ans = (i - 1) * i % mod;
	ans /= 2;
	ans = ans * ans % mod;
	return ans;
}

ll getans(ll n)
{
	ll ans = 0;
	ll inv = qpow(3, mod - 2);
	for (int i = 1; i <= n; i++)
	{
		ll tem = getC(i) * fac[i - 2] % mod * 4 % mod * inv % mod * qpow(fac[i], mod - 2) % mod;
		ans = ans + tem;
		ans %= mod;
	}
	ans = ans * qpow(n, mod - 2) % mod;
	return ans;
}
int main()
{

	ll n;
	init();
	while (cin>>n)
	{
		cout << getans(n) << endl;
	}
}

4.I Love Palindrome String。回文自动机+Hash。首先,根据字符串建立回文自动机,由于回文自动机上的每一个节点都是一个回文串,并且儿子和父亲的区别仅仅是两边的字母,那奇回文举例,我们从1号点搜索下去,对于每个节点,正反Hash一次,如果发现正反hash值相同,那么就符合条件,统计答案,继续向下搜索。这样当dfs结束时,就可以把所有的回文串给统计完。

#include<bits/stdc++.h>
using namespace std;

typedef unsigned long long ull;

struct str_hash {//单hash
    static const ull maxn = 3e5 + 666, p = 47, mod = 1e9 + 7;
    static ull pw[maxn];
    ull hash[maxn], revhash[maxn], len;

    str_hash() {
        if (pw[0] == 1)return;
        pw[0] = 1;
        for (ull i = 1; i < maxn; i++) {
            pw[i] = pw[i - 1] * p % mod;
        }
    }

    void ini() { len = 0; }

    void add(char ch) {
        //        cout<<"add" <<ch<<endl;
        len++;
        hash[len] = (hash[len - 1] * p + ch - 'a') % mod;
        revhash[len] = (revhash[len - 1] + pw[len - 1] * (ch - 'a')) % mod;
    }

    void del() {
        //        cout<<"del"<<endl;
        len--;
    }

    bool ok() {
        return hash[len] == revhash[len];
    }

    //注意没有下标检查
};
ull str_hash::pw[maxn];




struct palindrome_tree {
    static const int maxn = 3e5 + 666;
    int trans[maxn][26], len[maxn], suf[maxn], num[maxn], ans[maxn];
    //字典树一样,len代表回文长度,suf指向回文后缀,类似于fail指针,num是最长后缀的数量,经过calc之后是后缀数量
    int last, cnt;//last是上一个回文后缀,cnt是所有节点数

    str_hash hashmation;

    int new_node(int _len, int _suf, int _num) {//长度,后缀,数量
        for (int i = 0; i < 26; i++)trans[cnt][i] = 0;
        len[cnt] = _len;
        suf[cnt] = _suf;
        num[cnt] = _num;
        return cnt++;
    }

    void ini() {
        cnt = 0;
        int root_even = new_node(0, 1, 0);//=1
        int root_odd = new_node(-1, 1, 0);//=0
        last = root_odd;
    }

    void build(char* s, int lens) {//s是要建立回文自动机的字符串,下标从1开始
        for (int i = 0; i <= lens; i++) ans[i] = 0;
        for (int i = 1; i <= lens; i++)extend(s, i);
        calc();
    }

    void extend(char* s, int cur) {
        int w = s[cur] - 'a';//当前结点的值
        int p = last;//上一次匹配到的回文后缀
        while (s[cur - len[p] - 1] != s[cur]) p = suf[p];
        //现在p结点的cur儿子,要么是匹配成功的最长非自身回文后缀,要么是自身这一个字符

        if (!trans[p][w]) {//如果此回文后缀未出现过,要创建节点
            int v = suf[p];//我们来找他的suffix link回边,
            while (s[cur - len[v] - 1] != s[cur])v = suf[v];
            //此时意味着找到了suffix link 是v的儿子
            trans[p][w] = new_node(len[p] + 2, trans[v][w], 0);
        }

        last = trans[p][w];//这一次匹配到的回文后缀
        num[last]++;
    }


    void calc() {
        for (int i = cnt - 1; ~i; i--)num[suf[i]] += num[i];//结点创建顺序保证了suf[i]<i
    }

    void solve() 
    {
        hashmation.ini();
        dfs(1);
        hashmation.ini();
        dfs(0);
    }

    void dfs(int rt) 
    {
        for (int i = 0; i < 26; i++) 
        {
            if (trans[rt][i] != 0) 
            {
                hashmation.add('a' + i);
                if (hashmation.ok()) 
                {
                    ans[len[trans[rt][i]]] += num[trans[rt][i]];
                }
                dfs(trans[rt][i]);
                hashmation.del();
            }
        }
    }

}pat;

const int maxn = 3e5 + 666;
char s[maxn];

int main() {
    while (~scanf("%s", s + 1)) {
        int len = strlen(s + 1);
        pat.ini();
        pat.build(s, len);
        pat.solve();
        for (int i = 1; i <= len; i++) {
            printf("%d", pat.ans[i]);
            if (i != len) printf(" ");
        }
        printf("\n");
    }
}

5.后边的题目时队友搞的,还没写。

1012当时写了一个分治,每次用出现次数小于k的字符分割区间,然后T了。。。。。。,队友线段树写过了。

1002是一个很常见的DP,队友说树状数组搞一下即可。

1004上来看了我大概就感受到了这是一个边分治。

1008有技巧的网络流,暂时还没啥思路。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值