18.8.17 考试总结

高斯消元
【问题描述】
everlasting 觉得太无聊了,于是决定自己玩游戏!

他拿出了n 个小圆,第i 个的颜色是ai。接着他将这n 个小圆复制m 次并依次连接起来。

之后他发现其中有很多颜色相同的小圆,于是他决定:每有k 个连续颜色相同的小圆就将他们消去,

并将剩下的依次连接。(注意只会消除k个,即使有超过k 个)他将每次从头开始不断进行这个操作直到无法操作为止。

他想知道最后能剩下多少个小圆?


【输入格式】

从文件guass.in 中输入数据。

第一行三个正整数n,m,k,表示开始小圆的个数,复制次数和消除连续小圆
的个数。
第二行n 个正整数,第i 个数表示第i 个小圆的颜色。

 

【输出格式】

输出到文件guass.out 中。

一个整数,表示剩余小圆的个数。

 

这道题毫无算法可言.. 就是极端恶星的模拟模拟模拟 然后细节暴多 机房里面只有turuisen大佬花了三个小时搞出来了

然后就是lantx花了两个小时真 乱搞弄了95

我? 我一个样例都没过的错误程序还狗了40分...受宠若惊了

先把在一个循环里的连续k个相同都去掉,然后算出头和尾能消去的个数

,然后就是每个循环剩余长度*(m-1)+仅去掉同一循环的剩余数的个数。

为什么呢 画个图就清楚了

 

每个循环剩余长度就是天蓝色设为x 最后消去之后还剩下m * x 但是两头的湖蓝色并没有消去

湖蓝色长度和就为一整块长度 - x 所以总共长度就是一整块长度 + (m - 1) * x

但是可能他搞完之后中间的天蓝色凑到一起也可以被消去 特判一下就好了

代码

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

typedef long long ll;
const int N = 1e6 + 5;
int x,s[N][2],n,m,p,cnt,tot;
ll ans;

int main( ) {
    
    freopen("guass.in","r",stdin);
    freopen("guass.out","w",stdout);
    scanf("%d%d%d",& n,& m,& p);
    for(int i = 1;i <= n;i ++) {
        scanf("%d",& x);
        if(! cnt || s[cnt][0] != x) {
            s[++ cnt][0] = x;
            s[cnt][1] = 1;
        }
        else s[cnt][1] ++;
        if(s[cnt][1] == p) s[cnt][1] = 0,cnt --;
    }    
    for(int i = 1;i <= cnt;i ++) tot += s[i][1];
    int head = 1,tail = cnt;
    while(head < tail && s[head][0] == s[tail][0]) {
        if((s[head][1] + s[tail][1]) % p == 0) head ++,tail --;
        else {
            s[head][1] = (s[head][1] + s[tail][1]) % p;
            s[tail][1] = 0;
            break;
        }
    }
    if(head < tail) {
        for(int i = head;i <= tail;i ++) ans += (ll)s[i][1];
        ans = (ll)tot + 1LL * (m - 1) * ans; 
    }
    else {
        if(s[head][1] * m % p == 0) ans = 0;
        else {
            ans = (ll)s[head][1];
            ans = (ll)tot + (ll)ans * (m - 1);
            ans -= 1LL * s[head][1] * m - 1LL * s[head][1] * m % p;
        }
    }
    printf("%I64d",ans);
}

糖果镇
【问题描述】
yyc 来到了糖果镇,她在这里能得到好多好吃的棒棒糖^_^,糖果镇的道路是一个n*m方阵,

在每个点上会有一些糖果,但是yyc每到一个点都会拿走所有的糖果,并且她只能向下走或者向右走,

且要从起点(1,1)走到终点(n,m),否则她会被留在糖果镇贪婪的恶魔吃掉⊙o⊙

当然,为了不让一个人占有过多的棒棒糖,糖果镇决定让每一个人最后得到的糖果数对于p取模。

所以现在yyc想请你帮她算一算这样她最多能得到多少的棒棒糖。


【输入格式】
从文件candy.in中输入数据。

第一行3个正整数n,m,p

接下来m行,每行n个数,第i行第j列a[i][j]表示该点的糖果数。


【输出格式】
输出到文件candy.out中。

一行,一个整数,表示答案。

 

当m<=2时,枚举分界点,预处理前缀和就好了

对于另外20%,直接dp[i][j]表示到点i,j的最优答案

对于100%,我们可以枚举第二次下行的分界点,然后我们可以统计出第一次下行的影响,

就是第一行到x的前缀和减去第二行到x-1的前缀和,第二行就是到枚举端点的前缀和,

这样在已知第二次下行的分界点时,第二行和第三行的贡献是已知的,

我们只要找到模意义下第一行的最优贡献就好了,这个就可以用set维护,找前驱。

(如果是枚举第一行的分界点那就是倒着搞 这样子就能保证我能用的存了 不能用的没存)

第一次用set感觉好高级啊 z大爷tql...!!!!

代码

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

typedef long long ll;
const int N = 1e5 + 5;
int n,m;
ll a[4][N],S,ans = 0,dp[4][N],sum[4][N],mod;
set<ll>st;
set<ll>::iterator it;

void solve1( ) {
    
    if(m == 1) ans = S % mod;
    else {
        for(int i = 1;i <= n;i ++)
          ans = max(ans,(sum[1][i] % mod + sum[2][n] - sum[2][i - 1]) % mod);
    }
}

void solve2( ) {
    
    for(int i = 1;i <= m;i ++)
      for(int j = 1;j <= n;j ++) 
        dp[i][j] = max(dp[i][j],max(dp[i][j - 1],dp[i - 1][j])) + a[i][j];
    ans = dp[m][n];
}

void solve3( ) {
    
    for(int i = 1;i <= n;i ++) {
        ll x = (sum[1][i] - sum[2][i - 1] + mod) % mod;
        st.insert(x);
        ll m = (sum[3][n] - sum[3][i - 1] + sum[2][i] + mod) % mod;
        it = st.lower_bound(mod - m);
        if(it != st.begin( )) it --;
        ans = max(ans,(m + (*it)) % mod);
        it = st.end( );
        if(it != st.begin( )) it --;
        ans = max(ans,(m + (*it)) % mod);
    }
}

ll read( ) {
    
    ll ans = 0,t = 1;
    char x; x = getchar( );
    while(x < '0' || x > '9') {
        if(x == '-') t = -1;
        x = getchar( );
    }
    while(x >= '0' && x <= '9') {
        ans = ans * 10 + x - '0';
        x = getchar( );
    }
    return ans * t;
}

int main( ) {
    
    freopen("candy.in","r",stdin);
    freopen("candy.out","w",stdout);
    scanf("%d%d%I64d",& n,& m,& mod);
    for(int i = 1;i <= m;i ++) {
        for(int j = 1;j <= n;j ++) {
            a[i][j] = read( );
            sum[i][j] = sum[i][j - 1] + a[i][j];
        }
    }
    S = 0;
    for(int i = 1;i <= m;i ++) S += sum[i][n];
    if(m <= 2) solve1( );
    else if(S < mod) solve2( );
    else solve3( );
    printf("%I64d",ans);
}

游戏
【问题描述】 小明和小刚正在玩如下的游戏。首先小明画一个有N个顶点,M条边的有向图。

然后小刚试着摧毁它。在一次操作中他可以找到图中的一个点,并且删除它所有的入边或所有的出边。

小明给每个点定义了两个值:Wi+和Wi-。如果小刚删除了第i个点所有的入边他要给小明付Wi+元,

如果他删除了所有的出边就需要给小明付Wi-元。 找到小刚删除图中所有边需要的最小花费。


【输入格式】
从文件game.in中输入数据。
第一行有两个数N,M(1<=N<=100,1<=M<=5000)。
第二行有N个整数,描述了N个点的Wi+,同样的第三行是这N个点的Wi-。所有的费用都是正数并且不超过10^6。接下来的M行每行有两个数,代表有向图中相应的一条边。


【输出格式】
输出到文件game.out中。 输出一行一个整数,即小刚的最小花费。

 

这道题是原题了 感觉讲了好多次 

通过题目可以发现,我们要求的就是在使用最小的点权的情况下选中所有边,

每条边都可以被他的两个端点选中,且出入点权可能不同,于是就可以发现这是一个最小点权覆盖。 

建边: 

1>   先建立虚拟源S和汇T,把每个点拆成两个,ia,ib。

2>   从S向ia连一条流量为wi-的边,从ib向T连一条流量为wi+的边。

3>   原图中的边从ua向vb连一条流量无穷大的边。 

4>  然后跑最大流即可。

拆一拆点 其乐无穷 还有这道题好像必须要加当前弧优化和delta优化 不然看姜sir就活生生被卡成40分

代码

#include <bits/stdc++.h>
#define oo 1e9
using namespace std;

const int N = 500;
const int M = 1e5;
int n,m,src,sink,w,tot = 1,ans,q[N],h[N];
int head[N],nex[M],tov[M],f[M],dis[N];
bool vis[N];

void add(int u,int v,int w) {
    
    tot ++;
    nex[tot] = head[u];
    tov[tot] = v;
    f[tot] = w;
    head[u] = tot;
    tot ++;
    nex[tot] = head[v];
    tov[tot] = u;
    f[tot] = 0;
    head[v] = tot;
}

void add_edge( ) {
    
    src = 1,sink = 2 * n + 2;
    for(int i = n + 2;i <= 2 * n + 1;i ++) {
        scanf("%d",& w);
        add(i,sink,w);
    }
    for(int i = 2;i <= n + 1;i ++) {
        scanf("%d",& w);
        add(src,i,w);
    }
    for(int i = 1;i <= m;i ++) {
        int u,v;
        scanf("%d%d",& u,& v);
        add(1 + u,1 + n + v,oo);
    }
}

bool bfs( ) {
    
    memset(vis,0,sizeof(vis));
    memset(dis,0,sizeof(dis));
    int h = 0,t = 0;
    q[++ t] = src;
    vis[src] = true;
    while(h < t) {
        int u = q[++ h];
        for(int i = head[u];i;i = nex[i]) {
            int v = tov[i];
            if(f[i] && (! vis[v])) {
                vis[v] = true;
                dis[v] = dis[u] + 1;
                q[++ t] = v;
            }
        }
    }
    return vis[sink];
}

int dfs(int u,int delta) {
    
    if(u == sink) return delta;
    int res = 0;
    for(int & i = h[u];i;i = nex[i]) {
        int v = tov[i];
        if(f[i] && delta && dis[v] == dis[u] + 1) {
            int del = dfs(v,min(delta,f[i]));
            res += del;
            delta -=del;
            f[i] -= del;
            f[i ^ 1] += del;
        }
    }
    return res;
}

int main( ) {
    
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    scanf("%d%d",& n,& m);
    add_edge( );
    while(bfs( )) {
        for(int i = src;i <= sink;i ++) h[i] = head[i];
        ans += dfs(src,oo);
    }
    printf("%d",ans);
    return 0;
}

今天竟然是rank 2 有点小高兴..w

转载于:https://www.cnblogs.com/Rubenisveryhandsome/p/9494474.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值