SXOI2016 部分解题报告

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

T1 bridge

Description

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

Input

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

Output

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

Sample

Input

100 3
24 60
10 40
18 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

7
2 aa
3 aba
3 aaa
6 abaaba
5 aaaaa
4 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;
}
posted @ 2017-03-24 23:48  ljt12138  阅读(256)  评论(0编辑  收藏  举报