20170209考试

大佬们所说的水题考试

budget

题目
这里写图片描述

题解

与NOIP2006金明的预算方案类似,但是附件的数量是不确定的,所以用动态数组进行储存,先进行一次01背包,然后再用分组背包的算法解决问题,v、c两个数组记录有附件与没有附件的最优值。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#define maxn 110

using namespace std;
int n,m;
int x,y,z;
vector <int > a[maxn];
vector <int > b[maxn];
int p[maxn],v[300*maxn];

int main()
{
    freopen("budget.in","r",stdin);
    freopen("budget.out","w",stdout);
    scanf("%d%d",&n,&m);
    int N = 0;
    for(int i = 1;i <= m;i++){
        scanf("%d%d%d",&x,&y,&z);
        if(z == 0) 
          a[++N].push_back(x),b[N].push_back(x*y),p[i] = N;
        else a[p[z]].push_back(x),b[p[z]].push_back(x*y);
    }
    for(int i = 1;i <= N;i++){
        int c[300*maxn] = {0};
        for(int j = n;j >= a[i][0];j--)
            c[j] = v[j-a[i][0]] + b[i][0];
        for(int q = 1;q < a[i].size();q++)
           for(int j = n;j >= a[i][0]+a[i][q];j--)
              c[j] = max(c[j],c[j-a[i][q]]+b[i][q]);
        for(int j = 0;j <= n;j++)
           v[j] = max(v[j],c[j]);
    }
    cout<<v[n]<<endl;
    return 0;
}

color

题目

这里写图片描述
这里写图片描述

题解

来自JesseLiu大神的题解
这里写图片描述
对于这道题需要注意的是,一是空间会炸,二是存邻接表一定不要用结构体(TLE半个小时就是因为用结构体),三是能写读入优化就写读入优化,四是OJ里面不能加强行加栈这种东西。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#define maxn 110000
#define INF 0x7fffffff
#define ll long long
using namespace std;
int n,m,u,v,id,cnt,root;
int fa[maxn];
ll c[maxn],f[maxn],g[maxn],k[maxn];
int pt[maxn],st[maxn],nxt[maxn];
inline int gi() {
    int x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x;
}

inline void add(int x,int y)
{
    pt[++cnt] = y;
    nxt[cnt] = st[x];
    st[x] = cnt;
}

inline void dfs(int x){
    ll sigmaf = 0,sigmag = 0,sigmak = 0;
    for(int i = st[x]; i ;i = nxt[i]){
        dfs(pt[i]);
        sigmaf += f[pt[i]],sigmag += g[pt[i]],sigmak += k[pt[i]];
    }
    f[x] = c[x] + sigmak;
    g[x] = 2147483640;
    for(int i = st[x]; i ;i = nxt[i]){
        g[x] = min(g[x],sigmag+f[pt[i]]-g[pt[i]]);
    }
    g[x] = min(g[x],f[x]);
    k[x] = sigmag;
    k[x] = min(k[x],g[x]);
}

int main()
{
    freopen("color.in","r",stdin);
    freopen("color.out","w",stdout);
    //-----强行开栈----------------- 
    int size = 32 << 20; // 256MB
    char *p = (char*)malloc(size) + size;
    __asm__("movl %0, %%esp\n" :: "r"(p));
    //-----------------------------------
    n = gi();
    for(int i = 1;i <= n;i++){
        id = gi(),c[id] = gi(),u = gi();
        for(int j = 1;j <= u;j++)
          v = gi(),add(id,v),fa[v] = id;
    }
    for(int i = 1;i <= n;i++)  if(fa[i] == 0) root = i;
    dfs(root);
    cout<<min(f[root],g[root])<<endl;
    return 0;
} 

pupil

题目
这里写图片描述
这里写图片描述

题解

来自QT大佬的题解

Solution:
1.模型的建立:
<1>.该题的YesNo的判定就是说能不能把整个图给遍历完
<2>.易通过分析得到一个强联通分量中只要收买了一个点那么整个强联通分量就会全部被遍历。。<强连通分量的定义是任意两点可以互相到达>所以我们可以通过tarjan题中常用的办法对每个强联通分量进行缩点<把一个强联通分量等价为新图中的一个点>,而每个强联通分量的权值就是对在这个强连通分量中所有能被收买的权值取min(很显然吧);
<3>.进一步分析,缩完点后,新图是一个DAG(有向无环图(可用来装B)),那么我们考虑入度为0,并且能够收买的点。
<4>对于这些点,因为入度为0,所以为了完成对整个图的遍历,这些点就是一定要收买的,(不然不可能完成对整个图的遍历);
<5>那么我们从所有入度为0且能够收买的点开始对全图进行遍历,如果能够把图遍历完,那么最小的花费一定是所有入度为0且能够收买的权值加起来。<自己应该可以YY出来吧>,如果不能遍历完,就在每个不能被遍历的强联通分量中取min即可
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#define maxn 10020
#define INF 0x7fffffff
using namespace std;
int n,p,id,m,u,w,cnt,df = 0,top = 0,tot = 0;
int v[maxn],dfn[maxn],low[maxn],k[maxn],in[maxn],s[maxn],stack[5*maxn];
int head[maxn*5],to[maxn*5],nxt[maxn*5];
bool vis[maxn],c[maxn][maxn];
vector<int> h[maxn];
inline int gi(){
    int x = 0;
    char ch = getchar();
    while(ch < '0'||ch > '9')  ch = getchar();
    while(ch >= '0'&&ch <= '9') x = x*10+ch-'0',ch = getchar();
    return x;
} 

inline void link(int x,int y)
{
    to[++cnt] = y;
    nxt[cnt] = head[x];
    head[x] = cnt;
}

inline void tarjan(int x)
{
    vis[x] = 1;
    low[x] = dfn[x] = ++df;
    stack[++top] = x;
    for(int i = head[x]; i ;i = nxt[i]){
        int t = to[i];
        if(!dfn[t])  tarjan(t),low[x] = min(low[x],low[t]);
        else if(vis[t])  low[x] = min(low[x],dfn[t]);
    }
    if(low[x] == dfn[x]){
        tot++;
        int y;
        do{
            y = stack[top--];
            vis[y] = 0;
            k[y] = tot;
            v[x] = min(v[x],v[y]);
            h[tot].push_back(y);
            for(int i = head[y]; i ;i =nxt[i])  c[tot][to[i]] = 1;
        }while(x != y);
        s[tot] = v[x];
    }
}

inline void dfs(int x)
{
    for(int i = head[x]; i ;i =nxt[i]){
        int t = to[i];
        if(in[t] >0&&s[t] == INF){
           in[t]--;
           if(in[t] == 0)  in[t] = -1,dfs(t);
        }
    }
}

int main()
{
    freopen ("pupil.in","r",stdin);
    freopen ("pupil.out","w",stdout);
    cin>>n>>p;
    int o;
    for(int i = 1;i <= n;i++)  v[i] = INF;
    for(int i = 1;i <= p;i++)
       cin>>id,cin>>o,v[id] = o;
    cin>>m;
    for(int i = 1;i <= m;i++)
       cin>>u>>w,link(u,w);
    for(int i = 1;i <= n;i++)
       if(!dfn[i])
         tarjan(i);
    memset(head,0,sizeof(head)),cnt = 0;
    for(int i = 1;i <= tot;i++)
       for(int j = 1;j <= n;j++)
          if(k[j] != i&&c[i][j])  
          in[k[j]]++,link(i,k[j]);
    int ans = 0,flag = 1;
    for(int i = 1;i <= tot;i++)
       if(in[i] == 0){
          if(s[i] != INF)
            ans += s[i];
          else{
              flag = 0;  in[i] = -1; dfs(i);    
          }
       }
    if(flag)  cout<<"YES"<<endl<<ans<<endl;
    else{
        int t = INF;
        for(int i = 1;i <= tot;i++){
            if(in[i] == -1){
               for(int j = 0;j < h[i].size();j++)
                  if(h[i][j] < t)  t = h[i][j];
            }
        }
        cout<<"NO"<<endl<<t<<endl;
    }
    return 0;
} 

tree

题目
这里写图片描述
这里写图片描述

题解

解题思路 来自YWJ大神的题解
第一问:树链剖分的单点修改+区间查询。
第二问:离线做法。
    我们可以与第一问类比一下:对于第一问,所有对答案有贡献的点只有一个约束条件:该点在给定两点的路径上(或在给定点的子树上)。而一个点对答案的贡献就是它的权值,所以用树链剖分区间求和即可。
    再看看第二问:一个点要对答案有贡献,必须满足两个条件:(1)该点在给定两点的路径上(或在给定点的子树上);(2)该点的权值小于等于一个给定的值k。而每个点对于答案的贡献是1。
    如果只要求满足第一个条件,那么我们只需要像第一问一样区间求和就可以了。但是,还有第二个条件的约束。所以,我们需要保证:在询问时,只有权值小于等于k(k的意义见上)的点能对答案产生贡献。也就是说,只有权值小于等于k的点对应的dfs序在线段树中对应的点为1,其它都是0。
    所以,我们可以把树上的每一个点当做一次修改操作(把对应的dfs序在线段树中对应的值修改为1),每一个询问操作就是统计路径(子树)上有多少个1(其实就是一次区间求和,和第一问一模一样)。我们只需要在进行操作之前,对于所有的操作,以权值为关键字(修改操作的权值是对应的点权,询问操作的权值为对应的k)由小到大排序,权值相同的把修改放到查询之前。然后和第一问一样地跑就可以了。
数组含义
dep[]记录深度  
sz[]记录子树大小  
son[]记录重儿子  
top[]所在重链的顶端点
dfn[]该点的时间戳  
fa[]记录父亲 
endd[]这个子树的最末尾节点的dfn 
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cmath>
#define lson (x << 1)
#define rson (x << 1) | 1
#define ll long long
using namespace std;
const int maxn = 50010;
int n,m,x,y,tt,cnt,flag;
int nxt[3*maxn],to[maxn*3],head[maxn*3],ans[maxn];
int dep[maxn],sz[maxn],fa[maxn],son[maxn],top[maxn],dfn[maxn],endd[maxn],tr[maxn*4];
struct data{
    int id,t,x,y,z;
}g[maxn*3];

inline int gi()
{
    int x = 0,o = 1;
    char ch = getchar();
    while(ch != '-'&&(ch < '0'||ch > '9'))  ch = getchar();
    if(ch == '-')  ch = getchar(),o = -1;
    while(ch >= '0'&&ch <= '9')  x = 10*x+ch-'0',ch = getchar();
    return x*o;
}

inline void dfs1(int x,int d)
{
    dep[x] = d,sz[x] = 1;
    for(int i = head[x]; i ;i = nxt[i]){
        if(!dep[to[i]]){
           dfs1(to[i],d+1),sz[x] += sz[to[i]],fa[to[i]] = x;
           if(sz[to[i]] > sz[son[x]])    son[x] = to[i];
        }
    }
}

inline void dfs2(int x,int f)
{
    dfn[x] = ++cnt,top[x] = f;
    if(son[x])  dfs2(son[x],f);
    for(int i = head[x]; i ;i =nxt[i])
       if(to[i] != son[x]&&to[i] != fa[x])  dfs2(to[i],to[i]);
    endd[x] = cnt;
}

inline void update(int x,int l,int r,int num,int p)
{
    if(l == r){
        tr[x] = p;
        return ;
    }
    int mid = (l+r) >> 1;
    if(num <= mid)  update(lson,l,mid,num,p);
    else update(rson,mid+1,r,num,p);
    tr[x] = tr[lson] + tr[rson];
}

inline int query(int x,int l,int r,int xl,int xr)
{
    if(xl <= l&&r <= xr)  return tr[x];
    int mid = (l+r) >> 1;
    ll ret = 0; 
    if(xl <= mid)  ret += query(lson,l,mid,xl,xr);
    if(xr > mid)   ret += query(rson,mid+1,r,xl,xr);
    return ret;
}

inline int lca(int x,int y)
{
    int ans = 0;
    while(top[x] != top[y]){
        if(dep[top[x]] < dep[top[y]])  swap(x,y);
        ans += query(1,1,n,dfn[top[x]],dfn[x]);
        x = fa[top[x]];//top[x]已经计算过了 
    }
    if(dep[x] > dep[y])  swap(x,y);
    ans += query(1,1,n,dfn[x],dfn[y]);
    return ans;
}

inline bool cmp(const data xx,const data yy)
{
    if(xx.z != yy.z)  return xx.z < yy.z;
    return xx.id < yy.id;
}

int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    n = gi();
    for(int i = 1;i < n;i++){
        x = gi(),y = gi();
        to[++tt] = y,nxt[tt] = head[x],head[x] = tt;
        to[++tt] = x,nxt[tt] = head[y],head[y] = tt;
    }
    dfs1(1,1),dfs2(1,1);

    m = gi();
    for(int i = 1;i <= m;i++){
        flag = gi();
        if(flag == 1)  x = gi(), y = gi(), update(1,1,n,dfn[x],y);
        else if(flag == 2)  x = gi(), y = gi(), printf("%d\n",lca(x,y));
        else x = gi(),printf("%d\n",query(1,1,n,dfn[x],endd[x]));
    }
    m = gi();
    for(int i = 1;i <= m;i++){
        g[i].id = gi(),g[i].t = i;
        if(g[i].id == 1)  g[i].x = gi(),g[i].y = gi(),g[i].z = gi();
        else g[i].x = gi(),g[i].z = gi(); 
    }
    for(int i = 1;i <= n;i++)
       g[++m].id = 0,g[m].x = dfn[i],g[m].z = query(1,1,n,dfn[i],dfn[i]);
    memset(tr,0,sizeof(tr));
    sort(g+1,g+m+1,cmp);
    for(int i = 1;i <= m;i++){
        if(g[i].id == 0)  update(1,1,n,g[i].x,1);
        else if(g[i].id == 1)  ans[g[i].t] = lca(g[i].x,g[i].y);
        else ans[g[i].t] = query(1,1,n,dfn[g[i].x],endd[g[i].x]);
    }
    for(int i = 1;i <= m-n;i++)   printf("%d\n",ans[i]);
    return 0;
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值