总结:
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; }