福建省队集训被虐记——DAY3

昨天没写……今天补上吧

一如既往的跪了




棋盘

【问题描述】

给出一个N*M的方格棋盘,每个格子里有一盏灯和一个开关,开始的时候,所有的灯都是关着的。用(x, y)表示第x行,y列的格子。(x, y)的开关可以改变(x, y)中灯的状态,同时也可以改变满足|x’-x|=2,|y’-y|=1或者|x’-x|=1,|y’-y|=2的格子(x’, y’)的状态。改变状态的意思是,原来开着的灯会被关掉,原来关着的灯会被开起来。注意这边的改变状态是强制改变的。每个格子的开关最多只能按一次,求能使得所有灯都打开的方案数mod123456789的值。

【输入格式】

第一行,N,M。

【输出格式】

输出一个整数,表示答案。

【样例输入1】

2 3

【样例输出1】

4

【样例解释1】

XX.    .X.    XXX   .XX

XX.    XXX    .X.   .XX

其中X代表按这个格子的开关。

【样例输入2】

3 3

【样例输出2】

1

【样例解释2】

全取。

【数据规模与约定】

对于30%的数据,N*M<=20。

对于60%的数据,N*M<=200。

对于100%的数据,N,M<=150。



30分是暴力……不用讲

60分是高斯消元……因为最多只有n*m=150个点,高斯消元150^3可以过

100分是坑一点的加优化的高斯消元……在原来的基础上加个bitset还是什么的优化再乱搞+常数优化的就A了

题解上是这样描述正解的:

对于100%的数据,我们考虑缩减变量规模和方程规模。

假如我们确定了前2行和第一列的情况:

******

******

*x....

*.....

我们注意到,x格子的取法唯一。因为我们要满足最左上方那个格子。

同理,其他的格子的取法也可以如此推出来了。

现在我们的变量个数变成了N+M级别的。

那么同理,我们注意到,如果不是倒2行或者倒1列的格子,一定可以被满足。

所以我们只需要对这边的格子列方程即可。

现在变量数和方程数都是N+M级别的。高斯消元即可。

让我这连高斯消元都不会的情何以堪

// TopCoder Member SRM 494 Div 1 KnightsOut

#include <set>
#include <map>
#include <ctime>
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>

using namespace std;

const int Q = 123456789;
const int u[9][2] = {{1, 2}, {1, -2}, {2, 1}, {2, -1}, {-1, 2}, {-1, -2}, {-2, 1}, {-2, -1}, {0, 0}};
int N, M;

int calculatePower(int p)
  {
    int res = 1, i;
    for (i = 0; i < p; i ++)
      res = (res * 2) % Q;
    return res;
  }

int main() {
	freopen("chess.in", "r", stdin);
	freopen("chess.out", "w", stdout);
	scanf("%d%d", &N, &M);
    if (N == 1 || M == 1) {
		puts("1");
		return 0;
	}
    
    int i, j, k, d, t1, t2, cnt, tot = 0, loc;
    vector< vector< vector<int> > > eqn(N);
    for (i = 0; i < N; i ++)
      {
        eqn[i].resize(M);
        for (j = 0; j < M; j ++)
          eqn[i][j].assign(2 * M + N - 1, 0);
      }
    for (i = 0; i < M; i ++)
      eqn[0][i][i] = eqn[1][i][M+i] = 1;
    for (i = 2; i < N; i ++) eqn[i][0][2*M+i-2] = 1;
    for (i = 2; i < N; i ++)
      for (j = 1; j < M; j ++)
        {
          eqn[i][j][2*M+N-2] = 1;
          for (d = 0; d < 9; d ++)
            {
              t1 = i - 2 + u[d][0], t2 = j - 1 + u[d][1];
              if (0 <= t1 && t1 < N && 0 <= t2 && t2 < M)
                if (t1 != i || t2 != j)
                  for (k = 0; k < 2 * M + N - 1; k ++)
                    if (eqn[t1][t2][k] == 1) eqn[i][j][k] ^= 1;
            }
        }
    
    vector< vector<int> > mat(2 * M + N - 1);
    for (i = 0; i < 2 * M + N - 1; i ++)
      mat[i].assign(2 * M + N - 1, 0);
    for (i = 0; i < N; i ++)
      for (j = 0; j < M; j ++)
        if (i == N - 2 || i == N - 1 || j == M - 1)
          {
            for (d = 0; d < 9; d ++)
              {
                t1 = i + u[d][0], t2 = j + u[d][1];
                if (0 <= t1 && t1 < N && 0 <= t2 && t2 < M)
                  for (k = 0; k < 2 * M + N - 1; k ++)
                    if (eqn[t1][t2][k] == 1) mat[tot][k] ^= 1;
              }
            tot ++;
          }
    mat[tot ++][2*M+N-2] = 1;
    vector<int> res(2 * M + N - 1, 1);
    
    for (cnt = tot, i = 0; i < tot; i ++)
      {
        for (loc = 0; loc < tot; loc ++)
          if (mat[i][loc] == 1) break;
        if (loc >= tot)
          {
            if (res[i] == 1) return 0;
            continue;
          }
        for (cnt --, j = i + 1; j < tot; j ++)
          if (mat[j][loc] == 1)
            {
              res[j] ^= res[i];
              for (k = 0; k < tot; k ++) mat[j][k] ^= mat[i][k];
            }
      }
    printf("%d
", calculatePower(cnt));
    return 0;
  }





游行

【问题描述】

每年春季,在某岛屿上都会举行游行活动。

在这个岛屿上有N个城市,M条连接着城市的有向道路。

你要安排英雄们的巡游。英雄从城市si出发,经过若干个城市,到城市ti结束,需要特别注意的是,每个英雄的巡游的si可以和ti相同,但是必须至少途径2个城市。

每次游行你的花费将由3部分构成:

1.每个英雄游行经过的距离之和,需要特别注意的是,假如一条边被途径了k次,那么它对答案的贡献是k*ci,ci表示这条边的边权。

2.如果一个英雄的巡游的si不等于ti,那么会额外增加C的费用。因为英雄要打的回到起点。

3.如果一个城市没有任何一个英雄途经,那么这个城市会很不高兴,需要C费用的补偿。

你有无数个的英雄。你要合理安排游行方案,使得费用最小。

由于每年,C值都是不一样的。所以你要回答Q个询问,每个询问都是,当C为当前输入数值的时候的答案。

【输入格式】

第一行正整数N,M,Q;

接下来的M行,每行ai,bi,ci,表示有一条从ai到bi,边权为ci的有向道路。保证不会有自环,但不保证没有重边。

接下来Q行,每行一个C,表示询问当每次费用为C时的最小答案。

【输出格式】

Q行,每行代表一个询问的答案。

【样例输入】

6 5 3

1 3 2

2 3 2

3 4 2

4 5 2

4 6 2

1

5

10 

【样例输出】

6

21

32

【样例说明】

第一年的时候,C只有1。我们比较懒所以就不安排英雄出游了,需要支付6的费用。

第二年的时候,我们可以这么安排:

第一个英雄1->3->4->5,需要付2+2+2=6的费用,同时还要花5费用打的回家。再花5+5的费用来补偿2号城市和6号城市。

第三年略。

【数据范围】

对于20%的数据,N<=3。

另外40%的数据,Q<=5。

对于100%的数据,2<=N<=250,1<=M<=30000,1<=Q<=10000。

1<=ci<=10000,1<=C<=10000。




这种神题……表示我只会n^3Floyd一下然后就不会做了

正解是:首先Floyd。对于每一个询问,每个点拆点搞成二分图,左边n个右边n个。然后左右连边。对于两个点i , j连容量为1、费用为dist[i][j]的边。一开始费用直接取n*c,就是什么也不取。然后开始匹配,每次匹配之后都会使ans+=V-C。那么则可以分成合并成一个环、合并成树两种。注意到对于一个询问,C是给定的,V是固定的,所以直接预处理出所有增广路,然后读入c直接输出答案就行了

#include<cstdio>
#include<cstring>
const int N=505,M=130005;
int n,m,Q,l=1,S,T,x,y,z,a[N][N],d[N],pre[N],f[M*5],c[M];
int st[M],ed[M],data[M],cost[M],next[M],son[N];
bool b[N];
inline int min(int a,int b)
{return a<b?a:b;}
void addedge(int x,int y,int z,int c)
{
	st[++l]=x; ed[l]=y; data[l]=z; cost[l]=c; next[l]=son[x]; son[x]=l;
	st[++l]=y; ed[l]=x; data[l]=0; cost[l]=-c; next[l]=son[y]; son[y]=l;
}
int spfa()
{
	for (int i=S;i<=T;i++) d[i]=100000000,b[i]=0;
	int h=0,t=1; f[1]=S; d[S]=0;
	while (h<t){
		int i=f[++h]; b[i]=0; if (d[i]>=d[T]) continue;
		for (int p=son[i];p;p=next[p]) if (data[p]){
			int j=ed[p]; if (d[i]+cost[p]>=d[j]) continue;
			d[j]=d[i]+cost[p]; pre[j]=p; if (!b[j]) b[j]=1,f[++t]=j;
			}
		}
	for (int p=T;p!=S;p=st[p]) p=pre[p],data[p]--,data[p^1]++;
	return d[T];
}
int main()
{
	freopen("parade.in", "r", stdin);
	freopen("parade.out", "w", stdout);
	scanf("%d%d%d",&n,&m,&Q);
	S=0;T=n+n+1;
	memset(a,6,sizeof(a));
	for (int i=1;i<=m;i++)
	  {
	    scanf("%d%d%d",&x,&y,&z);
	    a[x][y]=min(a[x][y],z);
	  }
	for (int i=1;i<=n;i++)
	  for (int j=1;j<=n;j++)
	    for (int k=1;k<=n;k++)
	      if (a[i][k]+a[k][j]<a[i][j]) a[i][j]=a[i][k]+a[k][j];
	for (int i=1;i<=n;i++) addedge(S,i,1,0),addedge(i+n,T,1,0);
	for (int i=1;i<=n;i++)
	  for (int j=1;j<=n;j++)
	    addedge(i,j+n,1,a[i][j]);
	int now=spfa(),cnt=n;
	for (int i=0;i<=10000;i++){
		while (now==i) cnt--,now=spfa();
		c[i+1]=c[i]+cnt;
		}
	while (Q--) scanf("%d",&x),printf("%d
",c[x]);
	return 0;
}



模板题

【问题描述】

给一个N个点M条边的有向图,求边权和尽量大的一条简单路径。

简单路径的定义是不会经过重点的路径。

【输入格式】

第一行是数据编号。

第一行正整数N,M,表示点数和边数。

接下来的M行,每行a, b, c,表示从a到b一条边权为c的边。

【输出格式】

第一行输出你找到的简单路径的边的个数

接下来若干行输出你的边的下标。边从1开始标号。

【样例输入】

10

3 3

1 2 1

1 3 1

2 3 1

【样例输出】

2

1 3

【样例说明】

1->2,2->3最优。输出边的下标1和3。

【评分标准】

每个点有3个评分标准a1,a2,a3。

如果你的答案>=a1,得10分。

如果你的答案>=a2,得7分。

如果你的答案>=a3,得4分。

如果你的答案>0,得1分。

取能得到的最高的分。

测评方式和昨天一样,过一段时间会放出gen.exe,直接提交graph-upload.cpp即可。



这坑爹啊……首先引用题解对每一个数据点的描述:

0:2*N的网格图。

1:n=14,随便暴力。

2:树。

3:看起来是一棵树,实际上是一棵树加上一个环套树。

4:环套树,随机选取生成树很难A掉。

5:数据规模较小一些的环套树,随机选取多个生成树来做树的情况可以A。

6:环套树随机加上了一些边权为-3000000的边。

7: 仙人掌。

8:DAG。

9:分层图,一层内是一个环。

受到昨天黄巨大随机化70分的影响,我写的就是个随机加边+随机起点+随机退出的骗分,只不过多做几次。但是还是各种wa呀wa

代码太丑没脸见人了……就不贴了

正解是:

对于数据0,dp。

对于数据1,暴力乱搞。

对于数据2,dp。

对于数据3,枚举树上节点搞啊搞

对于数据4,树形dp。

对于数据5,如题解所说枚举生成树乱搞

对于数据6,删边完同3。

对于数据7,不要问我我也不会……关键是我忘了原来黄哥哥说怎么做了

对于数据8,有向无环图什么的随便拓扑排序之类的乱搞吧

对于数据9,这可以保存每层环的状态dp。

没有然后了……

——by zhber,转载请注明来源
原文地址:https://www.cnblogs.com/zhber/p/4036049.html