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