【NOIP2017】SummerTraining0707

这是一套大水题,8人AK,我100+0+0=100 rank29
T1乱搞题
T2树形DP,我不知道哪里搞炸了
T3更水,表示看到仙人掌就没去想了(其实是没想出)

看题解或代码的看目录,有索引。

T1

问题 A: 寻找羔羊
时间限制: 1 Sec 内存限制: 256 MB
题目描述
给定一个由小写字母组成的字符串,寻找包含“agnus”(羔羊)的子串的个数。注意:当且仅当两个子串的起始位置和终点不同时,这两个子串属于不同的子串。
输入
只有一个字符串,表示题中所述的字符串。

输出
仅一个数字,表示满足题意的子串个数。

样例输入
agnusbgnus

样例输出
6

提示

【样例解释】

6个子串分别是:agnus、agnusb、agnusbg、agnusbgn、agnusbgnu、agnusbgnus。

【数据规模和约定】

对于 40%的数据,字符串长度<=1000

对于 100%的数据,字符串长度<=30000

Solution

从头到尾遍历一次字符串,每碰到一次“agnus”,用“agnus”前面的字符数目乘上“agnus”后面的字符数目,即为含有当前“agnus”的字符串数目。
直接统计即可,不过要注意去重。
我们设找到的开头为x[i],末尾为y[i]=x[i]+4;
则个数为(x[i-1]~x[i]-1)*(y[i]+1~n),自己画一下图就行了

Code

#include <cstdio>
#include <cstring>

char s[30010];
int i,n,ans,x;

int main()
{
    scanf("%s",s+1);
    n=strlen(s+1); 
    ans=x=0; 
    for (i=1;i+4<=n;i++)
        if (s[i]=='a'&&s[i+1]=='g'&&s[i+2]=='n'&&s[i+3]=='u'&&s[i+4]=='s')
            {ans+=(i-x)*(n-i-3); x=i;}
    printf("%d\n",ans);
    return 0;
}

T2

问题 B: 统计损失
时间限制: 1 Sec 内存限制: 512 MB
题目描述
SJY有一天被LLT紧急召去计算一些可能的损失。LLT元首管理的SHB国的交通形成了一棵树,现在将会出现一颗陨石砸在SHB国中,并且陨石砸毁的必定是SHB国构成的交通树上的一条路径。SHB国的损失可表示为被砸毁的路径上的所有城市价值之积。现在还暂时无法确定陨石的掉落路线,所以LLT元首希望SJY能够告诉他SHB国在受到每一种砸毁方式后会受到的损失之和模10086之后的值。注意:单独一个节点也被认为是合法的路径。
输入
第1行一个数n,表示城市数。
第2行n个数,第i个数表示第i个城市的价值。
第3到n+1行,每行两个数u,v,表示城市u,v之间有一条道路。
输出
包含一个数,表示SHB国将受到的损失之和。
样例输入
5
7 6 6 1 1
1 2
2 3
2 4
1 5

样例输出
778

提示

【数据规模和约定】

n<=100;

n<=3000;

n<=100000

Solution

题意即统计所有的路径权值积的和。
算法1:
DFS每个点的路径,统计答案,时间复杂度O(n^2)

算法2:
树形DP,统计每个点的子树到当前点的路径积的和,再DFS逆序计算并合并答案即可,时间复杂度O(n)。合并时需注意合并一次的效率,要保证能够做到O(子树个数)合并,并且需要注意10086不能求逆元 。
PS:这道题目我想的是LCA求路径那样的算法,结果需要求逆元……卡死

Code

#include<bits/stdc++.h>
using namespace std;
#define MOD 10086
#define ll long long
const ll MAXN=100010;
ll Head[MAXN],Next[MAXN*2],To[MAXN*2];
ll a[MAXN],sum[MAXN];
ll n,tot,ans;

void add_eage(ll x,ll y)
{
    tot++;
    Next[tot]=Head[x];
    Head[x]=tot;
    To[tot]=y;
}
void dfs(ll u,ll pre)
{
    ans+=a[u]; sum[u]=0;
    for (ll i=Head[u];i;i=Next[i])
    {
        ll v=To[i];
        if (v==pre) continue;
        dfs(v,u);
        ans=(ans+sum[u]*sum[v]*a[u])%MOD;
        sum[u]=(sum[u]+sum[v])%MOD;
    }
    ans=(ans+sum[u]*a[u])%MOD;
    sum[u]=(sum[u]*a[u]+a[u])%MOD;
}
int main()
{
    scanf("%lld",&n);
    for (ll i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    for (ll i=1;i<n;i++)
    {
        ll x,y;
        scanf("%lld%lld",&x,&y);
        add_eage(x,y); add_eage(y,x);
    }
    ans=0;
    dfs(1,0);
    printf("%lld",ans); 
    return 0;
}

T3

问题 C: 简单题
时间限制: 1 Sec 内存限制: 512 MB
题目描述
dzy 手上有一张n 个点m 条边的联通无向图,仙人掌是一张每条边最多在一个简单环内的联通无向图。他想求这个无向图的生成仙人掌中最多有多少条边。
但是dzy 觉得这个问题太简单了,于是他定义了“美丽的生成仙人掌”,即在一个生成仙人掌中如果满足对于任意编号为i,j(i < j) 的两点,存在一条它们之间的简单路径上面有j-i+1 个点,则这个仙人掌是美丽的。
他现在想要知道这张图的美丽的生成仙人掌中最多有多少条边,你能帮帮他吗?
输入
第一行两个整数n,m。接下来m 行每行两个整数ui,vi,表示这两个点之间有一条无向边。保证图中没有自环。
输出
仅一行一个整数表示答案。
样例输入
2 1
1 2

样例输出
1

提示

【数据规模和约定】

对于10% 的数据,n <=10。


对于30% 的数据,n <=10^3。


对于100% 的数据,n <=10^5,m <= 2n。

Solution

算法1:
直接暴力枚举所有可能的边集,复杂度O(2n),期望得分10分

算法2:
研究题目可以发现,美丽的生成仙人掌中i 和i+ 1中必定有一条边。所以问题变成了:我们在一条链上加上若干条边使得得到的图是仙人掌且边数量最大。
显而易见,每一条非链边对应了链上的一个区间。于是问题就变成了选出最多数量的线段使得其互不相交。
这个可以用dp来实现,令F[i]为右端点最大为i 的区间的答案。则有:
F[i =max{F[j]+ 1(i和j之间存在一条边),f[i-1]}
时间复杂度O(n),期望得分100分,当然如果你一不小心写次了,时间复杂度O(n2),期望得分30分。

算法3:
显而易见,我们要求的是区间图的最大独立集。由于区间图把区间按右端点排序就是完美消除序列,直接贪心就可以了。时间复杂度O(nlogn)(用桶排可以做到O(n)),期望得分100分

PS:关键是用i,i+1代替i,j,我压根没想到

CODE

DP

#include<bits/stdc++.h>
using namespace std;
const int MAXN=100010;
int Head[MAXN],Next[MAXN*2],To[MAXN*2];
bool flag[MAXN]; 
int f[MAXN],n,m,tot;

void add_eage(int x,int y)
{
    tot++;
    Next[tot]=Head[x];
    Head[x]=tot;
    To[tot]=y;
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        if (x>y) swap(x,y);
        if (y==x+1 && !flag[x]) { flag[x]=true;continue;}
        add_eage(y,x);
    } 
    for (int i=1;i<=n;i++)
    {
        f[i]=f[i-1];
        for (int j=Head[i];j;j=Next[j])
            f[i]=max(f[i],f[To[j]]+1);
    }   
    printf("%d\n",f[n]+n-1);    
    return 0;
}

贪心

#include<bits/stdc++.h>
using namespace std;
const int MAXN=100010;
int n,m,ans,cnt,r;
bool flag[MAXN];

struct eage
{
    int x,y;
}e[MAXN*2];
bool cmp(eage a,eage b)
{
    return a.y<b.y;
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        if (x>y) swap(x,y);
        if (y==x+1 && !flag[x]) { flag[x]=true;continue;}
        cnt++;
        e[cnt].x=x; e[cnt].y=y;
    }
    sort(e+1,e+1+cnt,cmp);
    ans=n-1; r=0;
    for (int i=1;i<=cnt;i++)
        if (r<=e[i].x) { ans++; r=e[i].y; }
    printf("%d",ans);   
    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值