SXOI2016 部分解题报告

来源:互联网 发布:centos查看php版本 编辑:程序博客网 时间:2024/05/22 06:38

第四题还没做…先贴前三个题的。

T1 bridge

Description

有一支n个人的队伍要过一座限重为W的桥,我们规定这支队伍过桥时只能分组过,即当一组全部过去后,下一组才能接着过。给定每个队员的重量wi和过桥时间ti,一组人的过桥时间为花费时间最多的人的时间。问如何分组使得总的过桥时间最短。

Input

  • 第一行为两个正整数W,n
  • 接下来n行每行两个数ti,wi

Output

  • 仅一个数,为最小花费时间。

Sample

Input

100 324 6010 4018 50

Output

42

Hint

  • 对于40%的数据,n8
  • 对于70%的数据,n12
  • 对于100%的数据,n16,100W400,1t50,10w100

Solution

数据范围告诉我们应该考虑状压dp。

dp[S] 表示S中的人还未过河所用的时间,则有:

dp[S]=max{dp[SA]+T(A)|AS,W(A)W}

T(A),W(A)分别表示通过的时间最大值和重量总和。

现在问题转化为如何枚举集合的子集。如果用朴素的枚举只能拿到70分左右。从《挑战程序设计竞赛》上学到的一个方法则可以降低复杂度:

    sub = S;    do {        //...        sub = (sub-1)&S;    } while (sub > 1)

这样处理sub就枚举了2S

总的复杂度可以用:

1in(ni)2i

打个表就知道能O(能过)…

#include <bits/stdc++.h>using namespace std;int dp[1<<18], W, n;int w[20], t[20];int A[1<<18], M[1<<18];int dfs(int S){    if (S == 1) return 0;    if (dp[S] != -1) return dp[S];    dp[S] = 1e6;    int sap = S;    do {        int ans = A[sap], mx = M[sap];        if (ans <= W)            dp[S] = min(dp[S], dfs(S^sap)+mx);        sap = S&(sap-1);    } while (sap > 1);    //cout << S << " --> " << dp[S] << endl;    return dp[S];}int main(){    freopen("bridge.in", "r", stdin);    freopen("bridge.out", "w", stdout);    memset(dp, -1, sizeof dp);    scanf("%d%d", &W, &n);    for (int i = 1; i <= n; i++)        scanf("%d%d", &t[i], &w[i]);    int S0 = (1<<(n+1))-1;    for (int i = 2; i <= S0; i++) {        for (int j = 1; j <= n; j++)            if (i&(1<<j))                A[i] += w[j], M[i] = max(M[i], t[j]);    }    cout << dfs(S0) << endl;    return 0;}

T2 sequence

Description

定义F为斐波那契数列,被定义为:

F0=0,F1=1Fn=Fn1+Fn2,n>1

G为一个序列,定义为:

Gi=a1+a2+...+am=i,ak>0jmFaj

对于给定的n,求Gnmod109+7

Input

  • 一个数n

Output

  • 一个数Gnmod109+7

Sample

Input

3

Output

5

Hint

3=1+1+1  F1×F1×F1=1=2+1  F2×F1=1=1+2  F1×F2=1=3  F3=2

1+1+1+2=5

  • 对于30%的数据,n20
  • 对于50%的数据,n5000
  • 对于70%的数据,n107
  • 对于100%的数据,n1018

Solution

首先看样例解释很容易发现一个递推式:

Gn=1inFi×Gni

打表找规律会发现 i3,Gi=2Gi1+Gn2。现在来证明一下:首先可以证明G1=1,G2=2G1+G2=G3+1,然后用归纳法

2Gn1+Gn2=1in12FiGn1i+1in2FiGn2i=1in3Fi(2Gn1i+Gn2i)+2Fn1G0+2Fn2G1+Fn2G0=1in3FiGni+2Fn1+3Fn2  (1)

2Fn1+3Fn2=2Fn2+Fn1+Fn=n2inFiGni

因此:

(1)=Gn

原命题得证。然后只需要矩阵

[2110]

做快速幂取模就好了。注意特判。

#include <bits/stdc++.h>using namespace std;struct Matrix {    long long a[3][3];    Matrix()    { memset(a, 0, sizeof a); }    friend Matrix operator * (const Matrix &a, const Matrix &b)    {        Matrix C;        for (int i = 1; i <= 2; i++)            for (int j = 1; j <= 2; j++)                for (int k = 1; k <= 2; k++)                    (C.a[i][j] += a.a[i][k] * b.a[k][j]) %= 1000000007;        return C;    }};Matrix I(){    Matrix C;    memset(C.a, 0, sizeof C.a);    C.a[1][1] = C.a[2][2] = 1;    return C;}Matrix power(const Matrix &a, long long n){    if (n == 0) return I();    Matrix p = power(a, n>>1);    p = p*p;    if (n&1) p = p*a;    return p;}int main(){    freopen("sequence.in", "r", stdin);    freopen("sequence.out", "w", stdout);    long long n;    cin >> n;    if (n == 0) {cout << 1 << endl; return 0;}    if (n == 1) {cout << 1 << endl; return 0;}    Matrix M; M.a[1][1] = 2, M.a[1][2] = 1, M.a[2][1] = 1;    M = power(M, n-1);    cout << M.a[1][1] << endl;    return 0;}

T3 string

Description

给定n个回文串,s1,s2,...,sn。求有多少有序整数对 (i,j) 使得sisj 仍为回文串。

Input

  • 第一行一个整数n
  • 接下来n行,每行先是一个整数w,表示这个字符串的长度;然后是一个空格;之后给出这个长度为w的字符串。保证w0

Output

  • 仅一个正整数,表示有序对的个数。

Sample

Input

72 aa3 aba3 aaa6 abaaba5 aaaaa4 abba

Output

14

Hint

  • 14个有序对中,6个为 (i,i),其他8个分别为:
  • (1,3),(1,5),(3,5),(2,6),(3,1),(5,1),(5,3),(6,2)

  • m为总字符数。

  • 对于20%的数据,n100,m500
  • 对于40%的数据,n5000,m2000000
  • 对于100%的数据,n2000000,m2000000

Solution

当时考场上sdf一个女选手怒而碾过Orz……

我们用S1 表示S的反串。容易知道“穿脱原则”: (ab)1=b1a1 在这里成立。

考虑两个回文串a,b,根据定义有:

a=a1,b=b1ba=b1a1

满足题目中条件为:

ab=(ab)1

按照穿脱原则展开:

ab=b1a1

也就是:

ab=ba

所以原问题转化为了求ab=ba的对数。考虑用多项式哈希:

hash(ab)=hash(a)×base|b|+hash(b)hash(ba)=hash(b)×base|a|+hash(a)

移项并整理,得到:

hash(a)base|a|1=hash(b)base|b|1

可以看到两侧都变成了只含有一个量的表达式,成功地将两个量的关系转化成了一个量的性质,除法可以用模大素数下的乘法逆元处理,因此就可以丢进hash算了。

然而一个问题是这样做冲突严重。如果懒得写拉链怎么办呢?一个有趣的方法是考虑ab=ba必然有a[|a|]=b[|b|],而且他们是最高位,计算hash的时候乘了一个巨大的数。因此我们可以把hash值乘上这一位的ascii,就可以很大程度避免冲突了。

至于base的选取..亲测37效果最好…不知道为什么,可能是数据的缘故吧…

#include <bits/stdc++.h>using namespace std;long long mod = 2016122203ll, base = 37;long long power(long long a, long long n){    if (n == 0) return 1;    long long p = power(a, n>>1);    (p *= p) %= mod;    if (n&1) (p *= a) %= mod;    return p;}long long inv(long long a){ return power(a, mod-2); }long long hash_val(char str[]){    long long ans = 0;    for (char *p = str; *p != '\0'; ++p)        ((ans *= base) += (*p-'a'+1)) %= mod;    return ans;}map<long long, int> hash_set;int n, w;char str[2000005];long long cnt = 0;inline int read() {    int a = 0, c;    do c = getchar(); while(!isdigit(c));    while (isdigit(c)) {        a = a*10+c-'0';        c = getchar();    }    return a;}inline void read(char str[]){    char c;    int i = 0;    do c = getchar(); while(!isalpha(c));    while (isalpha(c)) {        str[i++] = c;        c = getchar();    }    str[i] = '\0';}int main(){    freopen("string.in", "r", stdin);    freopen("string.out", "w", stdout);    scanf("%d", &n);    for (int i = 1; i <= n; i++) {        w = read();        read(str);        long long val = inv(((power(base, w)-1)+mod)%mod);        (val *= hash_val(str)) %= mod;        cnt += hash_set[val*str[w-1]];        hash_set[val*str[w-1]]++;    }    cout << cnt*2+n << endl;    return 0;}
0 0
原创粉丝点击