noip57
题并不难,然而在一些细节和心态上出锅...
T1
考场想法:按题意写即可....
没啥好说的...
按题目说的判断不合法即可。
注意一些细节即可...
比如大小写和输出格式要求。
然后,自然数不包括负数...
T2
考场想法:想不出来暴力,那干脆写个假贪心吧,每回从前往后扫一遍串,碰见个 \(AP\) 或 \(PP\) 就删,码完后,觉得假斩了,但没管了就,直接下一道题了...
竟然A了
没啥好说的...
说一下b哥午饭时说的正解:
\(A\) 每回和 \(P\) 匹配显然最优,那就成括号匹配问题了,最后剩下的 \(P\) 两两消了即可。
T3
考场想法:看了半天题面,一直搞不懂菱形如何判,干脆写了个40pts的暴力,写完后继续看,发现其实是让某两个 \(P\) 从某一个共同的类派生出来的,并且这俩 \(P\) 有一个共同的派生出来的类。然后我就去想了并查集,发现不好搞。写暴力的话,觉得自己应该会调很长时间,此时还有大概1h,T4还没动。
然而一堆切的,没有想到 \(bitset\) ,还是菜了....
40pts:只判断前两个条件。
60pts:暴力去判断第三个条件。
100pts:
读题发现如果四个类构成菱形需要满足两个条件:
- x,y需要满足至少是由一个共同的类派生过来的。
- x,y之间没有派生关系。
所以对每个类开个 \(bitset\) 设为 \(vis\), \(vis_{i}\) 编号后第 \(i\) 个类是由那些类派生过来的。
每回新声明一个类,就去枚举输入的派生类对,先判断之前是否声明过了,然后去看是否其中有一对满足菱形的条件,则这个声明就是不合法的。
如果没有构成菱形,还需判断当前要声明的类之前是否声明过,都合法的话就给这个类编上号,并更新其 \(vis\) 即可。
这些都可以用 \(bitset+map\) 实现。
Code
#include<map>
#include<cstdio>
#include<cctype>
#include<bitset>
#include<string>
#include<iostream>
#define re register
using std::cin;
using std::cout;
using std::map;
using std::bitset;
using std::string;
#define MAX 1003
namespace some
{
struct stream
{
template<typename type>inline stream &operator >>(type &s)
{
bool w=0; s=0; char ch=getchar();
while(!isdigit(ch)){ w|=ch=='-'; ch=getchar(); }
while(isdigit(ch)){ s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); }
return s=w?-s:s,*this;
}
}oma;
auto line = []() { printf("\n"); };
auto begin = []() { printf("begin\n"); }; auto end = []() { printf("end\n"); };
}using namespace some;
namespace OMA
{
int n,cnt,tot;
string k,tmp[MAX];
map<string,int>id;
map<string,bitset<MAX> >vis;
auto main = []() -> signed
{
freopen("class.in","r",stdin); freopen("class.out","w",stdout);
oma >> n;
while(n--)
{
bool judge = true;
cin >> k >> tmp[cnt = 0];
while(tmp[cnt][0]!=';')
{
cin >> tmp[++cnt];
if(tmp[cnt][0]==';')
{ break ; }
}
//line(),begin(); printf("how many strings: %d\n",cnt);
for(re int i=1; i<cnt; i++)
{
if(!id[tmp[i]])
{ judge = false; break ; }
for(re int j=i+1; j<cnt; j++)
{
if(vis[tmp[i]][id[tmp[j]]]||vis[tmp[j]][id[tmp[i]]])
{ continue ; }
if((vis[tmp[i]]&vis[tmp[j]]).count())
{ judge = false; break ; }
}
if(!judge)
{ break ; }
}
if(!id[k]&&judge)
{
id[k] = ++tot;
//cout << k << '\n';
for(re int i=1; i<cnt; i++)
{
vis[k][id[tmp[i]]] = 1;
vis[k] |= vis[tmp[i]];
}
printf("ok\n");
}
else
{ printf("greska\n"); }
//end();
}
return 0;
};
}
signed main()
{ return OMA::main(); }
T4
考场想法:读完题就想个 \(n^{2}2^{n}\) 的zz做法,写到一半才发现连最低的档都过不去,于是再去读了遍题,发现自己 \(k-degree\) 子图的定义刚刚弄错了,我竟然nt到不去看图的解释。但仍然不知道该怎么写,然后突发奇想,求联通块,计算每个联通块的答案取最优即可想到这个是因为自己还是没搞懂\(kd\)子图,码完调完就已经11:50,还有十分钟结束,突然发现自己又又写假了,直接按图上画的,先找 \(1-d\) ,再删去度数为1的,依次推下去就行了,但已经没时间再写了,于是检查了一下代码,发现没什么问题,比赛就结束了。
这假做法竟然有35pts...
有负数,少拿5pts,可恶
40pts:求遍联通块,求答案取最优,注意常数项可能为负数,所以答案要设成 \(-INF\) 。
100pts:
就是考场上最后的想法...
先求出度数最大为多少,然后去枚举 \(k-degree\) 子图的度数。
由于从 \(k-1\) 到 \(k\) 过程中,删点的操作可能会让图变得不联通(可以参考题面中的图),所以要对于每一个对应 \(k-d\) 联通块都求一遍答案。
从 \(k\) 更新到 \(k+1\) 时,需要判断点的度数是否合法,包括两个方面,原先 \(k\) 不合法的,更新后肯定还是不合法,删掉一些 \(k\) 中合法的点后,可能会导致其他点的度数也不合法。删点操作可以用 \(topo\) 来实现。
注意一下度数为0的点,一开始就设置为不合法即可,还有上边说的负数问题。
Code
#include<queue>
#include<cstdio>
#include<cctype>
#define re register
#define MAX 1000003
#define int64_t long long
const int64_t INF = -1e18;
using std::queue;
namespace some
{
struct stream
{
template<typename type>inline stream &operator >>(type &s)
{
bool w=0; s=0; char ch=getchar();
while(!isdigit(ch)){ w|=ch=='-'; ch=getchar(); }
while(isdigit(ch)){ s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); }
return s=w?-s:s,*this;
}
}cin;
struct graph
{
int next;
int to;
}edge[MAX<<1];
int cnt=1,head[MAX];
auto add = [](int u,int v) -> void { edge[++cnt] = (graph){head[u],v},head[u] = cnt; };
auto max = [](int a,int b) -> int { return a>b?a:b; };
}using namespace some;
namespace OMA
{
bool vis[MAX],ban[MAX];
int n,m,M,N,B,xam,du[MAX];
int64_t ns,ms,bs,s=INF,k,sou;
void dfs(int u)
{
if(vis[u])
{ return ; }
vis[u] = true; ns++;
for(re int i=head[u],v; i; i=edge[i].next)
{
v = edge[i].to;
ms += !ban[v],bs += ban[v];
if(!vis[v]&&!ban[v])
{ dfs(v); }
}
}
auto topo = [](int lim) -> void
{
queue<int>q;
for(re int i=1; i<=n; i++)
{ if(du[i]==lim){ ban[i] = true; q.push(i); } }
while(!q.empty())
{
int u = q.front(); q.pop();
for(re int i=head[u],v; i; i=edge[i].next)
{
v = edge[i].to;
if(!ban[v])
{
if((--du[v])==lim)
{ ban[v] = true; q.push(v); }
}
}
}
};
auto main = []() -> signed
{
freopen("kdgraph.in","r",stdin); freopen("kdgraph.out","w",stdout);
cin >> n >> m >> M >> N >> B;
for(re int i=1,u,v; i<=m; i++)
{ cin >> u >> v; add(u,v),add(v,u); du[u]++,du[v]++; }
for(re int i=1; i<=n; i++)
{ if(!du[i]){ ban[i] = true; } xam = max(xam,du[i]); }
cnt = 0;
for(re int i=1; i<=xam; i++)
{
for(re int j=1; j<=n; j++)
{ vis[j] = false; }
for(re int j=1; j<=n; j++)
{
if(!vis[j]&&!ban[j])
{
ms = ns = bs = 0; dfs(j);
ms /= 2,sou = M*ms-N*ns+B*bs;
//printf("ns=%lld ms=%lld bs=%lld sou=%lld\n",ns,ms,bs,sou);
if(s<sou)
{ s = sou,k = i; }
else if(s==sou)
{ k = max(k,i); }
}
}
topo(i);
}
printf("%lld %lld\n",k,s);
return 0;
};
}
signed main()
{ return OMA::main(); }
最后两道题有些着急了,主要是因为题面一直没看明白,导致照着错误的理解来写,再看一遍题面,就把原先代码删了再写,很浪费时间,再加上自己觉得时间不够了,导致后两道题出问题,一些细节都没有注意到。
好吧,对于这套题来说,时间是够的,还是自己心态问题,类似的情况应当避免发生。