2016.10.20 [ TEST 30 ] 总结

一.第二饭堂
Problem:
定义一个长度为 n 回文串的价值为 k ,当且仅当其长度为 n/2 的前缀和后缀满足价值为 k1 的回文串定义。
根据定义,不是回文串的字符串(包括空串)价值为 0
现在给定一个回文串,要求求出其所有前缀的价值和。
例:
abacaba 的价值
=a(1)+ab(0)+aba(2)+abac(0)+abaca(0)+abacab(0)+abacaba(3)
=6.
Solution:
根据其定义我们可以的出这样一个结论:设 fi 表示前 i 个字符组成的字符串的价值,那么易得fi=fi/2+1。利用hash O(n) 判断是否是字符串,如果是则更新价值,维护价值同时把答案维护出来。
本题数据不强, int 自然溢出即可。
p.s. 某神犇写了马拉车……_ (:3_|<) _厉害了word哥

#include <stdio.h>
#include <string.h>
#define hash 107
long long l, r, pow = 1, ans, f[ 5000005 ], len;
char ch[ 5000005 ];
int main()
{
    freopen("hw.in", "r", stdin);
    freopen("hw.out", "w", stdout);
    scanf("%s", ch+1);
    len = strlen( ch+1 );
    for(int i = 1; i <= len; i++)
    {
        l = l * hash + ch[ i ];
        r = r + ch[ i ] * pow;
        pow *= hash;
        if(l == r) f[ i ] = f[ i/2 ] + 1;
        ans += f[ i ];
    }
    printf("%lld\n", ans);
    return 0;
}

以为用 longlong 自然溢出会稳一点,结果并无卵用,比 int 自然溢出慢了50+ms。
二.数字
Problem:
定义一个数字是好数字,当且仅当满足:
1.有 2×n 个数位, n 是正整数(允许好数字有前导0)。
2.构成它的每个数字都在给定的集合 S 中。
3.它的前 n 位之和与后 n 位之和相等 它的奇数位之和与偶数位之和相等。
已知 n , S 求合法的好数字个数(mod999983).
Solution:
由条件3合法数字要求前 n 位之和与后n位之和相等,不妨设 fij 表示前 i 个数之和为j的方案数,同时也可以表示 i 个奇数和为j的方案数。当然其中有重复的方案,设前n个数之和为事件 A ,n个奇数之和为事件B,由容斥原理可知最终答案为 A+BAB ,分奇偶两种情况维护一下即可。

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define mod 999983
const int inf = ~0u>>1;
int n, len_s, c[ 15 ], vis[ 15 ], cnt, maxn = -inf;
long long ans, t1, t2, f[ 1001 ][ 10001 ];
char s[ 15 ];
int main()
{
    freopen("digit.in","r",stdin);
    freopen("digit.out","w",stdout);
    scanf("%d", &n);
    scanf("%s", s + 1);
    len_s = strlen( s+1 );
    for(int i = 1; i <= len_s; i++)
        if(!vis[ s[ i ] - '0' ])
        {
            c[ ++cnt ] = s[ i ] - '0';
            vis[ s[ i ] - '0' ] = 1;
            maxn = max(maxn, c[ cnt ]);
        }
    f[ 0 ][ 0 ] = 1;
    for(int i = 0; i < n; i++)
        for(int j = 0; j <= maxn*i; j++)
            for(int k = 1; k <= cnt; k++)
                    ( f[ i + 1 ][ j + c[ k ] ] += f[ i ][ j ] ) %= mod;
    for(int i = 0; i <= maxn * n; i++)
        (ans += f[ n ][ i ] * f[ n ][ i ]) %= mod;
    (ans <<= 1) %= mod;
    int a = (n+1) >> 1;
    int b = n >> 1;
    for(int i = 0; i <= a * maxn; i++)
        (t1 += f[ a ][ i ] * f[ a ][ i ]) %= mod;
    for(int j = 0; j <= b * maxn; j++)
        (t2 += f[ b ][ j ] * f[ b ][ j ]) %= mod;
    ans -= t1 * t2;
    printf("%lld\n", (ans % mod + mod) % mod);
    return 0;
}

三.树上三角形
Problem:
给定一棵 n 个点的点权树,每次进行询问或修改操作,询问操作:每次询问一对 (u,v) ,问是否能在 u v 简单路上(包括 u , v),取得三个点权,以这三个权值为边长构成一个三角形(输出 Y or N)。修改操作:将一个点的权值修改。(点权大小 <=2311 <script id="MathJax-Element-7980" type="math/tex"><=2^{31}-1</script>)
Solution:
如果我们考虑选择哪三个点构成三角形,显然这种方法是复杂而不可行的,正难则反,考虑什么样的数列可以使不合法的数尽量多。
假设有三点权值 a1<=a2<=a3 ,那么不合法的方案即为 a1+a2<=a3 ,当 a1+a2=a3 时,可使不合法的区间尽量长,我们可以发现斐波那契数列(1,1,2,3,5…)满足这一性质,由于点权限制在 int 范围内,所以最长不合法序列只有 50 个数。当询问两点之间点的个数大于 50 时直接输出 Y ,小于 50 的暴力验证即可。

#include <stdio.h>
#include <algorithm>
using namespace std;
int n, q, w[ 100001 ], t, a, b, s, e;
struct tree
{
    int to,next;
}edge[ 200001 ];
int head[ 100001 ],tot;
void insert(int x, int y)
{
    edge[ ++tot ].to = y;
    edge[ tot ].next = head[ x ];
    head[ x ] = tot;
    edge[ ++tot ].to = x;
    edge[ tot ].next = head[ y ];
    head[ y ] = tot;
}
int fa[ 100001 ],dep[ 100001 ],cnt;
long long que[ 100001 ];
void dfs(int x, int y)
{
    dep[ x ] = dep[ y ] + 1;fa[ x ] = y;
    for(int i = head[ x ]; i; i = edge[ i ].next)
        if(edge[ i ].to != y)
            dfs(edge[ i ].to,x);
}
void query(int x, int y)
{
    cnt = 0;
    if(dep[ x ] < dep[ y ]) swap(x, y);
    while(cnt<=50 && dep[ x ] != dep[ y ])
    {
        que[ ++cnt ] = w[ x ];
        x = fa[ x ];
    }
    while(cnt <= 50 && x != y)
    {
        que[ ++cnt ] = w[ x ];
        que[ ++cnt ] = w[ y ];
        x = fa[ x ];y = fa[ y ];
    }
    que[ ++cnt ] = w[ x ];
    if(cnt >= 50)
    {
        printf("Y\n");
        return;
    }
    sort(que + 1, que + cnt + 1);
    for(int i = 1; i + 2 <= cnt; i++)
        if(que[ i ] + que[ i+1 ] > que[ i+2 ])
        {
            printf("Y\n");
            return;
        }
    printf("N\n");
    return;
}
int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    scanf("%d%d",&n,&q);
    for(int i = 1; i <= n; i++)
        scanf("%d", &w[ i ]);
    for(int i = 1; i < n; i++)
    {
        scanf("%d%d", &s, &e);
        insert(s, e);
    }
    dfs(1, 0);
    for(int i = 1; i <= q; i++)
    {
        scanf("%d%d%d", &t, &a, &b);
        if(t) w[ a ] = b;
        else query(a, b);
    }
    return 0;
}

p.s.注意点权可以卡到 int 上限,两点权相加可能会爆 int

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值