您的位置:首页 > 其它

ZOJ 3352-3361||Monthly July 2010

2015-07-20 00:11 337 查看
按照难度从简单到难。不过榜歪了,准确来说是按照过题人数从多到少。

D题:题意是有三种赔率,现在问你有没有方案使得无论哪种结果出现都可以赚钱?

一开始的思路特别蠢,但是我忘记是啥了,等我明天去翻翻草稿纸再回来写。

翻到了。TAT 就是穷举方案,然后算出每个钱数用了多少,直接找规律,找了半天都没算出钱数要用多少……

其实设三个未知数列出不等式,就能知道。只要1/a+ 1/b+ 1/c 小于1就一定可以赚钱了,直接这样写会wa,记得要加上浮点误差的判断。或者是因为只有两位小数,直接把浮点数乘以一百然后加上一个EPS转换成整数就可以了。

H题:模拟题。我的姿势被批评很扭曲,但是一炮过,我很开心~~~~

E题:给你一些钱,让你怎么分三种赔率得到的钱最多。本来是三千毫秒,然后想着10的八次方暴力,然后循环里面大概有五十多步,TAT,WA了。

然后我仔细研究了样例。发现a的个数是(bc)* n/(ab+bc + ac)酱紫的,然后就做了,然而wa了,正确的做法应该是算出这个i之后往前延伸两个往后延伸两个,也就是i-2遍历到i+2.那么为什么要枚举呢?首先要先知道,为什么a的个数是(bc)* n/(ab+bc + ac)酱紫呢??晚上再写

A题:题意是有一个有向图,有“一面!!!”白旗和“一面!”黑旗。每人可以移动旗子,不能移动算输。问你先手想要收益最大,第一步有几种方案。

想起那个异或为0的也是问第一步有几种方案,当时推了一整场比赛。还有那个sg函数真的要好好看看了!!!!!然而和这一道题没啥关系。

这题就是用三维dp[i][j][k],i表示当前白旗位置,j表示当前黑旗位置,k表示我往下搜索之前场上的钱数,dp值存的是搜索结束后最大的收益。

对白旗跑一遍dfs,对黑旗跑一遍dfs。

至于方案数的判断,我原来用的做法是在main里面判断几种方案然后算,然而这样的话相当于转换了先手作为后手,会wa。要直接用记忆化搜索检查第一个人,同时加一个判断是否是第一步。

dp如果更新过就不用更新。

注意会出现负数。然后我开了大概8*(10^6)的数组。然后所有dp下标加上一个更正值就好啦。

神奇的事情是调用了dp[i][j][-1]居然不会报错。而是=0,简直神奇。

/*
dp[i][j][k]表示白棋在i,黑棋在j,然后现在局面上可以获得的是k
转移的就是前面的相反数的最大值(表示我走完这一步可以得到的钱数)上一步是对手走的我不管
TAT最后的结果就是终态,遍历i可走的地方,j可以走的地方
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define SIZE_D 55
#define SIZE_B 2605
#define INF 0x3f3f3f3f
using namespace std;
int digit[SIZE_D];
//const int INF = 0x3f3f3f3f;
int X,Y;
struct pp
{
int to,next;
}pic[SIZE_B];
int e,head[SIZE_D];
void init()
{
e = 0;
memset(head,-1,sizeof(head));
}
void addedge(int x,int y)
{
pic[e].to = y;
pic[e].next = head[x];
head[x] = e++;
}

const int alter = 1300;
int dp[SIZE_D][SIZE_D][SIZE_B];
int finalres,sum;
int dfs(int white,int black,int all)
{
if (dp[white][black][alter+all]!= -INF){
return dp[white][black][alter+all];
}
int res = -INF;
for (int i = head[white]; i != -1; i = pic[i].next){
int v = pic[i].to;
dfs(v,black,all + digit[v]);
if (res  <= -dp[v][black][alter+all + digit[v]]){
res = -dp[v][black][alter+all + digit[v]];
if (white == X && black == Y){
if (finalres < res){
finalres = res;
sum = 1;
}else if (finalres == res){
sum++;
}
}
}
res = max(res,-dp[v][black][alter+all + digit[v]]);
}
//printf("1st    res = %d\n",res);
for (int i = head[black]; i != -1; i = pic[i].next){
int v = pic[i].to;
dfs(white,v,all - digit[v]);
if (res <= -dp[white][v][alter+all- digit[v]]){
res = (-dp[white][v][alter+all- digit[v]]);
if (white == X && black == Y){
if (finalres < res){
finalres = res;
sum = 1;
}else if (finalres == res){
sum++;
}
}
}
}
if (res == -INF){
res = -all;
if (white == X && black == Y)
sum = 1;
}
return dp[white][black][alter+all] = res;
}
int main()
{
int N,M;
while (~scanf("%d %d %d %d",&N,&M,&X,&Y)){
for (int i = 0; i < N; i++)
scanf("%d",&digit[i]);
init();
for (int i = 0; i < M; i++){
int tempx,tempy;
scanf("%d %d",&tempx,&tempy);
addedge(tempx,tempy);
}
for (int i = 0; i <= N; i++)
for (int j = 0; j <= N; j++)
for (int k = 0; k < SIZE_B-1; k++)
dp[i][j][k] = -INF;
sum = 0;
finalres = -INF;
dfs(X,Y,1);
printf("%d %d\n",finalres,sum);
}
return 0;

}

G题:有个妹子每天住在一个地方,她可以获得一定的收益,每一天得到的钱不同,然后她可以搬到指定的地方numi,那个地方的人类可以帮她搬,但是这个人类搬到某个指定的地方,妹子就要请这个人吃饭,花费指定的钱数bgmi,问妹子在d天晚上之前可以得到的最大钱数。

题解:dp[i][j][k] .i代表这是第i天下午,j代表今晚准备住在j号房子,k代表今晚住完了之后,累计在j住了k天。注意,一定能够要清楚dp中每一维代表的是今晚的情况还是昨晚的情况,很容易纠结不清。

然后这题有个巨大的问题。就是妹子一个下午可以搬家很多很多次啊!天啊,要跑一遍弗洛伊德得出每两个点的最小花费啊!注意正常的弗洛伊德,自己到自己的花费是0,但这道题自己到自己的花费要初始化成INF哟!然后和其他点等同,假如跑了一圈回到自己,那么今晚就相当于第1天而不是第k+1天。

弗洛伊德模板!注意不同问题有不同的初始化方法!!!

int d[SIZE_N][SIZE_N];
void warshall_floyd(int V)
{
for (int k = 0; k < V; k++)
for (int i = 0; i < V; i++)
for (int j = 0; j < V; j++)
d[i][j] = min(d[i][j],d[i][k] + d[k][j]);
}


这道题的ac代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define SIZE_N 330
#define INF 0x3f3f3f3f
using namespace std;
int a[SIZE_N][5],b[SIZE_N],c[SIZE_N],dp[SIZE_N][SIZE_N][5];
int m[SIZE_N][SIZE_N][5];
int mnum[SIZE_N];

int d[SIZE_N][SIZE_N]; void warshall_floyd(int V) { for (int k = 0; k < V; k++) for (int i = 0; i < V; i++) for (int j = 0; j < V; j++) d[i][j] = min(d[i][j],d[i][k] + d[k][j]); }
int main()
{
//freopen("input.txt","r",stdin);
int n,day;
while (~scanf("%d %d",&n,&day)){
memset(d,INF,sizeof(d));
for (int i = 0; i < n; i++){
scanf("%d %d %d",&a[i][1],&a[i][2],&a[i][3]);
a[i][4] = a[i][3];
//int m;
scanf("%d",&mnum[i]);
for (int j = 0; j < mnum[i]; j++){
scanf("%d %d",&m[i][j][0],&m[i][j][1]);
d[i][m[i][j][0]] = m[i][j][1];
}
}
memset(dp,-1,sizeof(dp));

warshall_floyd(n);

//dp[0][0][0] = 0;
dp[1][0][1] = 0;//a[0];//下午还是0的
for (int i = 1; i <= day; i++)
for (int j = 0; j < n; j++)
for (int k = 1; k <= 4; k++){

if (dp[i][j][k] == -1) continue;
//printf("\n\n i = %d j = %d k = %d\n",i,j,k);
int now = dp[i][j][k] + a[j][k];//当前收益
for (int l = 0; l < n; l++){
if (now < d[j][l]) continue;
dp[i+1][l][1] = max(now - d[j][l], dp[i+1][l][1]);
}
if (k <= 3){
dp[i+1][j][k+1] = max(now,dp[i+1][j][k+1]);
}else{
dp[i+1][j][4] = max(now,dp[i+1][j][4]);
}
}
int sum = 0;
for (int j = 0; j < n; j++)
for (int k = 1; k <= 4; k++)
sum = max(sum, dp[day][j][k]);
printf("%d\n",sum);
}
return 0;
}


J题:模拟题。


注意的地方:“namely shifting amnesty for yellow cards from the end of the first round until after the quarter-finals
instead”

首先 四分之一决赛指的是倒数第三场……好吧……决赛<——半决赛<——四分之一决赛……

然后,这句话的 意思是指进入第五场时,前三场的黄牌就可以自动消失,第四场的黄牌保留。

还有,题目中也没说给的比赛号码是按顺序的。

以上。但我根本没做这道题,反正我模拟题的姿势渣的一比。

B题:有四种反转方案,只要你选择一种能经过最少次这种翻转可以得到全白的界面,如果有多种选择序号最小。

题解:首先弄一个可爱的矩阵,三行的。枚举每个方块出现的位置,也就是k从0到1<<15。只要k中哪个位置有1,就代表这个位置转一次。然后,注意有很多都是右侧一列最中间只有一个1的,把他们集体逆时针90度,然后只需要遍历第一行就知道放还是不放啦。

存矩阵的时候把每个角度都转一遍存下来~一共存四个矩阵,然后每个矩阵一行的十五个数字压缩成二进制表示一个数字。然后每种方法放的时候遍历第一行就好啦。

存方法数矩阵的时候有一个特别厉害的姿势可以存下k从0到1<<15中所有的矩阵。

所有的都逆时针九十度了所以记得原矩阵也要逆时针90度。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: