总结:

B.字典树和暴力
D.构造题
E.建最大生成树
F.树链剖分
H.二分答案和树上dfs
K.二分图和二进制枚举


小结:
----构造难顶,还要多搞搞
----练习最大生成树
----终于遇到一道icpc树链剖分,熟悉了一下线段树推标记
----二分答案
----二分图练得少,运用好多啊,还能判奇偶环....


B. Prefix Code

大致题意:给n个字符串,判断是否可以根据字符串前缀进行识别所有字符串。
分析:字典树和map暴力都行,给的字符串长度最多10,首先所有字符串有相同的肯定输出 No, map暴力处理出每个字符串的前缀判断是否出现过.

#include<bits/stdc++.h>

using namespace std;


map<string,int> mp;

int main()
{
    int t,cas=1;
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);

    cin>>t;
    while( t-- )
    {

        int n,flag=0;
        cin>>n;
        mp.clear();
        vector<string> p;
        for( int i=1;i<=n;i++ )
        {
            string s;
            cin>>s;
            p.push_back(s);
            if( mp.count(s) ) flag=1;
            else mp[s]++;
        }
        if( flag )
        {
            printf("Case #%d: ",cas++);
            puts("No");
            continue;
        }
        for( int i=0;i<p.size();i++ )
        {
            for( int j=0;j<p[i].size()-1;j++ )
            {
                string s2=p[i].substr(0,j+1);
                if( mp.count(s2) )
                {
                     flag=1;
                     break;
                }
            }
        }
        printf("Case #%d: ",cas++);
        if( flag )
        {
            puts("No");
            continue;
        }
        else puts("Yes");
    }
}

字典树:

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;

int tree[maxn][11],cnt;
int tag[maxn];

bool add( char *s )
{
    int len=strlen(s),root=0;
    bool flag=0;
    for( int i=0;i<len;i++ )
    {
        int x=s[i]-'0';
        if( i==len-1 && tree[root][x] ) flag=true;
        if( !tree[root][x] ) tree[root][x]=++cnt;
        root=tree[root][x];
        if( tag[root] ) flag=true;
    }
    tag[root]=true;
    return flag;
}

int main()
{
    int t,cas=1;
    scanf("%d",&t);
    while( t-- )
    {
        int n;
        scanf("%d",&n);
        int flag=0;
        memset(tree,0,sizeof(tree));
        memset(tag,0,sizeof(tag));
        cnt=0;
        char s[11];
        for( int i=1;i<=n;i++ )
        {
            scanf("%s",s);
            if( add(s) ) flag=1;
        }
        printf("Case #%d: ",cas++);
        if( flag ) puts("No");
        else puts("Yes");
    }
    return 0;
}

D. Spanning Tree Removal

大致题意: 给一个n阶完全图,请尽可能构造多的边互异的生成树.
分析:构造题, 我还特意搜了完全图中生成树的,发现有好多论文。 这个只能盲猜推结论构造了.

#include<bits/stdc++.h>

using namespace std;

int main()
{
    int t,cas=1;
    scanf("%d",&t);
    while( t-- )
    {
        int n;
        scanf("%d",&n);
        printf("Case #%d: %d\n",cas++,n/2);
        for( int i=1;i<=n/2;i++ )
        {
            int l=i;
            int r=(l+n-2)%n+1;
            for( int j=1;j<n;j++ )
            {
                printf("%d %d\n",l,r);
                if( j&1 ) l=l%n+1;
                else r=(r+n-2)%n+1; 
            }
        }
    }
    return 0;
}

E. Cave Escape

大致题意:n * m单元格,给定起点坐标和终点坐标。还有每个位置权值的计算方式,从起点走到终点,第一次经过的位置可以获得一个能量,能量的计算方式是上一个位置的权值乘以当前位置的权值,求获得最大能量数.
分析:显然就是求一颗最大生成树。这里有个技巧就是存边索引可以是边权大小进行分类。( val<10000 )

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=1e6+10;

int fa[maxn];
int sr,tr,sc,tc;
int v[1005][1005];
int x[maxn],a,b,c,p;
int n,m,t;

vector< pair<int,int> > e[10005];


int Find( int x )
{
    return fa[x]==x ? x : fa[x]=Find(fa[x]);
}

void add( int x,int y ,int z )
{
    e[z].push_back( make_pair( x,y ) );
}

int main()
{
    int cas=1;
    scanf("%d",&t);
    while( t-- )
    {
        for( int i=0;i<=10000;i++ ) e[i].clear();

        scanf("%d%d%d%d%d%d",&n,&m,&sr,&sc,&tr,&tc);
        scanf("%d%d%d%d%d%d",&x[1],&x[2],&a,&b,&c,&p);
        for( int i=3;i<=n*m;i++ ) x[i]=(a*x[i-1]+b*x[i-2]+c)%p;
        for( int i=1;i<=n;i++ )
        {
            for( int j=1;j<=m;j++ )
            {
                fa[(i-1)*m+j]=(i-1)*m+j;
                v[i][j]=x[(i-1)*m+j];
            }
        }
        for( int i=1;i<=n;i++ )
        {
            for( int j=1;j<=m;j++ )
            {
                int now=(i-1)*m+j ,nex1=i*m+j, nex2=(i-1)*m+j+1;
                if( i+1<=n ) add( now,nex1,v[i][j]*v[i+1][j]);
                if( j+1<=m ) add( now,nex2,v[i][j]*v[i][j+1]);
            } 
        }
        ll ans=0;
        int cnt=0;
        for( int i=10000;i;i-- )
        {
            for( auto it:e[i] )
            {
                if( cnt==n*m-1 ) break;
                int u1=it.first,v1=it.second;
                int uu=Find(u1), vv=Find(v1);
                if( uu==vv ) continue;
                fa[uu]=fa[vv];
                ans+=i;
                cnt++;
            }
            if( cnt==n*m-1 ) break;
        }
        printf("Case #%d: %lld\n",cas++,ans);
    }
    return 0;
} 

F. A Simple Problem On A Tree

大致题意:给定树上n个结点和n-1条边,每个点都有权值,对结点有四种操作.

  • 1 u v w 将编号为[u,v]的点权该为w
  • 2 u v w 将编号为[u,v]的点权加上w
  • 3 u v w 将编号为[u,v]的点权乘上w
  • 4 u v w 对编号[u,v]的点权的三次方求和输出.

分析:很显然是一道树链剖分题(线段树推标记) 大胖题.有四种操作.考线段树 乘法、覆盖、累加 推标记.
三次方的标记变化具体看代码注释.

#include<bits/stdc++.h>
#define ls cur<<1
#define rs cur<<1|1

using namespace std;

const int maxn=1e5+10;
const int mod=1e9+7;
typedef long long ll;

//*/ 建边 
struct node{
    int to,next;
}edge[maxn<<2];

int tot,head[maxn];


void add( int u,int v )
{
    edge[tot]={v,head[u]};
    head[u]=tot++;
} 

void init()
{
    memset(head,-1,sizeof(head));
    tot=0;
}

int rk[maxn]; // [线段树编号] 对 应结点号 结点初始赋值数组 
ll val[maxn];
//*/ 线段树维护 


struct node1{
    int l;
    int r;
    ll sum1,sum2,sum3,mul,lazy;
}t[maxn<<2];



void pushup( int cur ) //ok 
{
    t[cur].sum1=( t[ls].sum1+t[rs].sum1 )%mod;
    t[cur].sum2=( t[ls].sum2+t[rs].sum2 )%mod;
    t[cur].sum3=( t[ls].sum3+t[rs].sum3 )%mod;
}

// x^3 --> ( x+z )^3 = x^3+3*x^2*z+3*x*z^2+z^3
// x^3 --> (ax)^3

// x^2 --> (x+z)^2 = ( x^2+2*x*z+z^2 ) 
// x^2 --> (ax)^2

void cal( int cur,ll x,ll y )
{
    ll len=1ll*( t[cur].r-t[cur].l+1 );
    ll a3=((x*x)%mod)*x%mod, a2=((3ll*x)%mod*x%mod)*y%mod, a1=(((3ll*x)%mod*y)%mod)*y%mod ,z=((y*y)%mod*y)%mod*len%mod;
    t[cur].sum3=( (a3*t[cur].sum3)%mod+(a2*t[cur].sum2)%mod+(a1*t[cur].sum1)%mod+z )%mod;
    a2=(x*x)%mod;  a1=((2ll*x)%mod*y)%mod; z=((y*y)%mod*len)%mod;
    t[cur].sum2=( (a2*t[cur].sum2)%mod+(a1*t[cur].sum1)%mod+z )%mod;
    t[cur].sum1=( x*t[cur].sum1%mod+(y*len)%mod )%mod;
    t[cur].mul=(t[cur].mul*x)%mod;
    t[cur].lazy=( t[cur].lazy*x%mod+y )%mod;
}

void pushdown( int cur ) //ok 
{
    if( t[cur].mul!=1ll || t[cur].lazy )
    {
        cal( ls,t[cur].mul,t[cur].lazy );
        cal( rs,t[cur].mul,t[cur].lazy );
        t[cur].mul=1ll;
        t[cur].lazy=0;
    }
}



void build( int cur ,int l,int r ) 
{
    t[cur].l=l;t[cur].r=r;
    t[cur].mul=1ll;
    t[cur].lazy=t[cur].sum1=t[cur].sum2=t[cur].sum3=0ll;
    if( l == r )
    {
        t[cur].sum1=val[rk[l]];
        t[cur].sum2=t[cur].sum1*t[cur].sum1%mod;
        t[cur].sum3=t[cur].sum2*t[cur].sum1%mod;
        return;
    }
    int mid=(l+r)>>1;
    build(ls,l,mid);
    build(rs,mid+1,r);
    pushup( cur );
}

void update( int cur,int L,int R ,ll x,ll y ) 
{
    int l=t[cur].l,r=t[cur].r;
    if( L<=l && r<=R )
    {
        cal(cur,x,y);
        return;
    } 
    pushdown( cur );
    int mid=(l+r)>>1;
    if( L<=mid ) update(ls,L,R,x,y);
    if( R>mid ) update(rs,L,R,x,y);
    pushup( cur );
}


ll get_Sum( int cur,int L,int R ) 
{
    int l=t[cur].l,r=t[cur].r;
    if( L<=l && r<=R )    return t[cur].sum3;
    ll ans=0;
    pushdown( cur );
    int mid=(l+r)>>1;
    if( L<=mid ) ans+=get_Sum(ls,L,R);
    ans%=mod;
    if( R>mid ) ans+=get_Sum(rs,L,R);
    ans%=mod;
    return ans;
}


//*/ 树链剖分 


int fa[maxn],dep[maxn],siz[maxn],top[maxn];
// 父亲 深度 大小 重链的头 
int v[maxn],son[maxn],dfn[maxn],w[maxn],tim;
//  [结点]权值 [结点]重儿子 时间戳(编号) 结点权值的[dfs序] 计数器 


void dfs1( int u,int f ) //找重链 
{
    fa[u]=f;    // 父节点 
    dep[u]=dep[f]+1; //当前深度 
    siz[u]=1;
    int maxsize=-1;
    for( int i=head[u];~i;i=edge[i].next )
    {
        int v=edge[i].to;
        if( v==f ) continue;
        dfs1(v,u);
        siz[u]+=siz[v];
        if( siz[v]>maxsize )
        {
            maxsize=siz[v];
            son[u]=v;
        }
    }
} 

void dfs2( int u,int t )
{
    dfn[u]=++tim;
    top[u]=t;
    w[tim]=v[u];
    rk[tim]=u;
    if( !son[u] ) return;
    dfs2(son[u],t);
    for( int i=head[u];~i;i=edge[i].next )
    {
        int v=edge[i].to;
        if( v==fa[u] || v==son[u] ) continue;
        dfs2(v,v);
    }
}


void mchain( int x,int y,ll mu ,ll ad ) //x-y路径上的点操作 z 
{
//    z%=mod; 
    while( top[x]!=top[y] )
    {
        if( dep[top[x]]<dep[top[y]] ) swap(x,y);
        update(1,dfn[top[x]],dfn[x],mu,ad);
        x=fa[top[x]];
    }
    if( dep[x]>dep[y] ) swap(x,y);
    update(1,dfn[x],dfn[y],mu,ad);
}

ll query( int x,int y ) 
{
    ll ans=0; 
    while( top[x]!=top[y] )
    {
        if( dep[top[x]]<dep[top[y]] ) swap(x,y);
        ans+=get_Sum(1,dfn[top[x]],dfn[x]);
        ans%=mod;
        x=fa[top[x]];
    }
    if( dep[x]>dep[y] ) swap(x,y);
    ans+=get_Sum(1,dfn[x],dfn[y]);
    return ans%mod;
}


int main()
{
    int t,cas=1;
    scanf("%d",&t);
    while( t-- )
    {
        tim=0;
        int n;
        scanf("%d",&n);
        init();
        for( int i=1;i<n;i++ )
        {
            int u,v;
            scanf("%d%d",&u,&v);
            add(u,v);
            add(v,u); 
        }
        for( int i=1;i<=n;i++ ) 
        {
            scanf("%lld",&val[i]);
            son[i]=0;
            dep[i]=0;
        }

        int r=1; //树的根节点 
        dep[1]=1;
        dfs1(r,r);
        dfs2(r,r);
        build(1,1,n);
        printf("Case #%d:\n",cas++);
        int q;
        scanf("%d",&q);
        while( q-- )
        {
            int opt,u,v;
            ll va;
            scanf("%d%d%d",&opt,&u,&v);
            if( opt==4 ) printf("%lld\n",query(u,v) );
            else
            {
                scanf("%lld",&va);
                if( opt==1 ) mchain(u,v,0ll,va);
                else if( opt==2 ) mchain(u,v,1ll,va);
                else mchain(u,v,va,0ll);
            }
        }
    }
    return 0;
}

H. Tree Partition

大致题意:n结点的树,请你划分成k个分区,当然就是去掉k-1条边,我们定义分区的值为分区中点权之和.你的得分是k个分区中值最大的。请你划分这棵树使得你的得分最小.
分析:二分答案,然后进行dfs check. %%

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

const int maxn=1e5+10; 

int t,n,k;
ll f[maxn],w[maxn];
vector<int> g[maxn];

int dfs( int u,int fa,ll mx )
{
    vector<ll> p;
    int cnt=0; // 表示当前节点向下的分区 
    for( auto v : g[u] )
    {
        if( v!=fa ) 
        {
            cnt+=dfs(v,u,mx);
            p.push_back(f[v]);
        }
    }
    sort(p.begin(),p.end());
    f[u]=w[u];
    for( auto v:p )
    {
        if( f[u]+v<=mx ) f[u]+=v;
        else cnt++;
    }
    return cnt;
}

int main()
{
    int cas=1;
    scanf("%d",&t);
    while( t-- )
    {
        scanf("%d%d",&n,&k);
        for( int i=1;i<=n;i++ )  g[i].clear();
        for( int i=1;i<n;i++ )
        {
            int u,v;
            scanf("%d%d",&u,&v);
            g[u].push_back(v);
            g[v].push_back(u);
        }
        ll l=0,r=0,ans=0;
        for( int i=1;i<=n;i++ ) 
        {
            scanf("%d",&w[i]);
            l=max(l,w[i]);
            r+=w[i];
        }
        while( l<=r )
        {
            ll mid=(l+r)>>1;
            if( dfs(1,0,mid)<k ) 
            {
                ans=mid;
                r=mid-1;
            }
            else l=mid+1;
        }
        printf("Case #%d: %lld\n",cas++,ans);
    }
}

K. Color Graph

大致题意:一张图,n个点m条边,初始所有边为白边,你可以将白边涂成红边.求最大红边数,并且不存在红边奇环.
分析:一共16个点边最多就是120条,显然是数位dp或者暴力枚举等方法.二分图可以判断奇偶问题.那么我们可以用俄二进制表示点选或者不选,然后暴力枚举每条边判断边的两个端点是否在当前集合和集合外,如果是就将这条边涂成红色,否则不涂,不断更新答案.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn=305;

struct edge
{
    int x,y;
}e[maxn];


int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);cout.tie(0);

    int t;
    cin>>t;
    int cas=1;
    while( t-- )
    {
        int n,m;
        cin>>n>>m;
        for( int i=0;i<m;i++ )
        {
            cin>>e[i].x>>e[i].y;
            e[i].x--; 
            e[i].y--;
        }
        int ans=0;
        for( int i=0;i<(1<<n);i++ ) 
        {
            int tmp=0;
            for( int j=0;j<m;++j )
            {
                int t1=i>>e[j].x & 1;
                int t2=i>>e[j].y & 1;
                if( t1+t2==1 ) tmp++;
            }
            ans=max(ans,tmp);
        }
        printf("Case #%d: %d\n",cas++,ans);
    }
    return 0;
}