AtCoder Beginner Contest 163(D,E(区间dp),F(树上路径问题))

题目链接

今天的题都很不错

D - Sum of Large Numbers

AtCoder Beginner Contest 163(D,E(区间dp),F(树上路径问题))_第1张图片

题意:求至少选k个数和的 种类数。

做法:刚开始感觉很难,涉及大数和、方案数。考虑k=2  由于n+1个数是连续的,那我选最小的k个数 求和 :mi   和最大的k个数求和:mx   那么mi~mx的数都能被表示出来(自己想想)。

如果k不同会不会出现和相同的,答案是不会,因为每个数有个很大的基数:10^100 k每大一个,就是大了10^100

 

#include
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define per(i,a,b) for(int i=a;i>=(b);--i)
#define mem(a,x) memset(a,x,sizeof(a))
#define pb push_back

#define mk make_pair
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}
const double pi=acos(-1);
const ll mod=1e9+7;
map< pair ,ll>mp,mp1;
int n,k;
const int N=2e5+10;
ll a[N];
void solve()
{
    scanf("%d%d",&n,&k);
    ++n;
    rep(i,1,n) a[i]=i-1;

    rep(i,1,n)
    {
        a[i]+=a[i-1];
    }

    ll ans=0;
    rep(i,k,n)
    {
        ll mi=a[i];
        ll mx=a[n]-a[n-i];
        ans=(ans+mx-mi+1)%mod;
    }

    cout<

 

 

E - Active Infants

AtCoder Beginner Contest 163(D,E(区间dp),F(树上路径问题))_第2张图片

题意:重新排序a[i]  每个a[i]的贡献是 a[i]*| i -j |  j  a[i]重新排序的下标。求重新排序后每个a[i]贡献之和 的最大值

做法:区间dp 一个很明显的做法是先考虑最大的数,然后每次往最左边或者最右边放,但是数可能是有重复的,所以不能用普通的贪心

设dp[l][r]为区间l,r 放数 的最大值。值从大到小枚举,区间从大到小即可。

#include
#define rep(i,a,b) for(int i=a;i<=(b);++i)
#define per(i,a,b) for(int i=a;i>=(b);--i)
#define mem(a,x) memset(a,x,sizeof(a))
#define pb push_back

#define mk make_pair
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b) { return b?gcd(b,a%b):a;}
const double pi=acos(-1);
const int N=2e3+10;
int n;

struct node{
    ll w;
    int id;
}a[N];
bool cmp(node a,node b)
{
    return a.w>b.w;
}

ll dp[N][N];
ll dfs(int pos,int  l,int r)
{
    if(pos>n) return 0;
    if(dp[l][r]!=-1) return dp[l][r];
    ll &ans=dp[l][r];

    ll t1=a[pos].w*abs(a[pos].id-r);
    ll t2=a[pos].w*abs(a[pos].id-l);
    t1+=dfs(pos+1,l,r-1);
    t2+=dfs(pos+1,l+1,r);
    ans=max(t1,t2);
    return dp[l][r];
}
int main()
{
	cin>>n;
	rep(i,1,n)
	{
	    cin>>a[i].w;
	    a[i].id=i;
	}
    sort(a+1,a+1+n,cmp);
    mem(dp,-1);
    //rep(i,1,n) printf("%d ",a[i].w);
    //puts("");

	ll ans=dfs(1,1,n);
	printf("%lld\n",ans);
}

F - path pass i

AtCoder Beginner Contest 163(D,E(区间dp),F(树上路径问题))_第3张图片

题意:给你一颗树,以及每个节点的颜色,问你 设颜色k =1....n   计算包含颜色k的路径数  输出ans[k] k从1到n

做法:由于颜色是有重复的,直接计算涉及容斥,容斥方面不好怎么考虑,那就反着做,总的路数减去不含颜色k的路径数

如何计算 不含颜色k的路径数?看官方题解:

AtCoder Beginner Contest 163(D,E(区间dp),F(树上路径问题))_第4张图片

看不懂。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。

那就研究别人的代码;

 

个人理解:核心思想:设cnt[v]为当前 v 子树时  不合法的点数cnt[v] 核心代码:cnt[col[v]]+=sz[col[v]]

计算:

ll pre=all-cnt[cs[v]];//前面计算过的 
      sub[v]+=dfs(u,v);
      ll nxt=all-cnt[cs[v]];//当前全部合法 
      ans[cs[v]]-=(nxt-pre)*(nxt-pre-1)/2;//所有的减去计算过的 

感觉有点讲不清。待补吧。。先贴别人的代码,推了几个样例,有点懵懵懂懂的,不知道怎么描述好点。。

 

#include
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
using namespace std;
typedef long long ll;
const int N=2e5+10;
vectorG[N];
int n,col[N],cnt[N],all;
ll ans[N],sz[N];
ll dfs(int u,int fa)
{
    ll tmp=cnt[col[u]];//保存其他树的cnt
    sz[u]=1;
    for(int v:G[u]){
        if(v==fa) continue;
        ll l=all-cnt[col[u]];//子树前
        sz[u]+=dfs(v,u);
        ll r=all-cnt[col[u]];//遍历子树后
        ll len=r-l;
        ans[col[u]]-=(len*(len-1)/2);
    }
    cnt[col[u]]=tmp+sz[u];//每次col[u]的颜色  重新被当前u全部覆盖
    all++;
    return sz[u];
}
int main()
{
    scanf("%d",&n);
    rep(i,1,n) scanf("%d",&col[i]);
    rep(i,2,n)
    {
        int u,v;scanf("%d%d",&u,&v);
        G[u].push_back(v);
        G[v].push_back(u);
    }



    rep(i,1,n) ans[i]=1ll*n*(n-1)/2;//所有路径
    dfs(1,-1);
    for(int i=1;i<=n;++i){//除了子树,从根节点到最近的颜色 联通块也要去掉
        //printf("i:%d cnt:%d ans:%lld\n",i,cnt[i],ans[i]);
        ll len=n-cnt[i];
        ans[i]-=(len*(len-1)/2);
    }
    for(int i=1;i<=n;++i) ans[col[i]]++;//每个单独的点是一条路径
    for(int i=1;i<=n;++i) printf("%lld\n",ans[i]);
}

 

你可能感兴趣的:(AtCode题解)