noip53
T1
考场写了个很假的贪心,只有20pts。
正解:
把所有的 \(i\) 向 \(f_{i}\) 连一条边,会形成个内向基环树森林。
先求出对于每个物品,得到最大的价值所对应的按钮,和次大的价值所对应的按钮。
求次大的原因是当你的图中出现环时,会有一个点不能被它最大的那个按钮给按完,会剩下1个,因为在这之前你按的那个按钮的商品就被按完了。
所以把这条最大的边,换成次大的边来替代即可。
每次跳最大价值所对应的按钮,跳到环上就换成次大的,答案减去对应的差值即可。
这个过程实际用循环就可以实现。
然而我老写假,所以还是用的dfs
Code
#include<cstdio>
#include<climits>
#define MAX 100003
#define INF INlatex
#define re reglatex
#define int64_latex
namespace somelatex
{
struct stream
{
template<typename type>inline stream &operator >>(type &s)
{
int w=1; s=0; char ch=getchar();
while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
return s*=w,*this;
}
}cin;
auto min = [](int a,int b) -> int { return a<b?a:b; };
}using namespace some;
namespace OMA
{
int n;
int64_t ans;
struct node
{ int f,c,d,a; }my[MAX];
int val[MAX],xam1[MAX],xam2[MAX],nim;
int cnt,cir[MAX];
void dfs(int u)
{
if(cir[u]==cnt)
{ ans -= nim; return ; }
if(cir[u])
{ return ; }
cir[u] = cnt;
//printf("u=%d\nbefore: %lld\n",u,ans);
ans += 1ll*val[xam1[u]]*my[u].a;
//printf("after: %lld\n",ans);
nim = min(nim,val[xam1[u]]-val[xam2[u]]);
if(xam1[u]!=u)
{ dfs(xam1[u]); }
}
auto main = []() -> signed
{
freopen("goods.in","r",stdin);
freopen("goods.out","w",stdout);
//freopen("my.out","w",stdout);
cin >> n;
for(re int i=1,f,c,d,a; i<=n; i++)
{ my[i] = (node){(cin >> f,f),(cin >> c,c),(cin >> d,d),(cin >> a,a)}; }
for(re int i=1; i<=n; i++)
{
val[i] = my[my[i].f].d-my[i].c;
//printf("%d\n",val[i]);
if(val[i]>val[xam1[my[i].f]])
{ xam2[my[i].f] = xam1[my[i].f],xam1[my[i].f] = i; }
else if(val[i]>val[xam2[my[i].f]])
{ xam2[my[i].f] = i; }
}
for(re int i=1; i<=n; i++)
{ if(!cir[i]){ nim = INF,cnt++; dfs(i); } /*printf("xam1=%d xam2=%d\n",xam1[i],xam2[i]);*/ }
printf("%lld\n",ans);
return 0;
};
}
signed main()
{ return OMA::main(); }
/*
5
2 4 5 5
5 7 7 5
5 3 5 4
3 2 3 3
3 1 4 5
*/
/*
5
4 1 2 3
5 1 3 7
1 1 1 5
2 4 6 7
3 6 7 7
*/
T2
考场写了个假做法,极限是 \(O(n^{3})\) 的暴力,时间上没问题,但正确性是假的,然而仍有20pts。
正解:
首先答案串 \(p\) 一定是输入串的子串,因为最后一次插入。
先按照暴力来枚举答案串,通过长度+字符出现次数先判断一下是否合法。
然后检验,暴力删正着做20pts,反着做没试,但正反一块会T。
所以考虑按题解说的用dp来检验当前串是否合法。
设 \(dp_{i,j}\) 表示区间 \([i,j]\) 是否能合法。
如果 \(j-i+1\) 是 \(len\) 的倍数,那么它就是一段合法的区间。
如果不是倍数,那么当多余的部分为 \(p\) 的前缀,其他的为若干段 \(p\) 时,该区间为合法区间。
转移时考虑第 \(j\) 个字符:
\(j\) 和之后的零散部分拼成一段,即\([i,j]\) 中有前缀:
\(j\) 是夹在零散部分之间的整块:
记忆化一下就能跑的飞快。
Code
#include<map>
#include<vector>
#include<cstdio>
#include<cctype>
#include<cstring>
#define MAX 203
#define re register
using std::map;
using std::vector;
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;
}using namespace some;
namespace OMA
{
int t,len1;
bool dp[MAX][MAX];
char s[MAX],now[MAX];
int cnt[26],tub[26][2];
vector<char>ans;
map<vector<char>,bool>vis;
auto check = [](int len2) -> bool
{
for(re int i=1; i<=len1; i++)
{ dp[i][i] = now[1]==s[i],dp[i][i] = 1; }
for(re int len=2; len<=len1; len++)
{
char pre = now[(len-1)%len2+1];
for(re int i=1,j; i<=len1-len+1; i++)
{
j = i+len-1,dp[i][j] = dp[i][j-1]&(s[j]==pre);
for(re int k=1; k*len2<=len&&!dp[i][j]; k++)
{ dp[i][j] |= dp[i][j-k*len2]&dp[j-k*len2+1][j]; }
}
}
return dp[1][len1];
};
auto main = []() -> signed
{
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
cin >> t;
while(t--)
{
scanf("%s",s+1); len1 = strlen(s+1);
for(re int i=0; i<=25; i++)
{ cnt[i] = 0; }
for(re int i=1; i<=len1; i++)
{ cnt[s[i]-'a']++; }
//for(re int i=0; i<=25; i++)
//{ printf("cnt[%c]=%d\n",i+'a',cnt[i]); }
vis.clear(),ans.clear();
bool flag,judge = false; int cot;
for(re int len2=1; len2<=len1; len2++)
{
if(!(len1%len2))
{
//printf("len=%d\n",len2);
cot = len1/len2,flag = true;
for(re int i=0; i<=25; i++)
{ if(cnt[i]%cot){ flag = false; break ; } tub[i][0] = cnt[i]/cot; }
//printf("%d\n",len2);
if(!flag)
{ continue ; }
//printf("%d\n",len2);
for(re int i=1; i<=len1-len2+1; i++)
{
flag = true;
for(re int j=0; j<=25; j++)
{ tub[j][1] = tub[j][0]; }
for(re int j=1; j<=len2; j++)
{
now[j] = s[i+j-1];
if(--tub[now[j]-'a'][1]<0)
{ flag = false; break ; }
}
if(!flag)
{ continue ; }
vector<char>tmp;
for(re int j=1; j<=len2; j++)
{ tmp.push_back(now[j]); }
if(vis[tmp])
{ continue ; }
vis[tmp] = true;
if(check(len2))
{
if(!judge)
{ ans = tmp,judge = true; }
else if(ans[0]-'a'>tmp[0]-'a')
{ ans = tmp; }
}
}
if(judge)
{ break ; }
}
}
//printf("\nans: ");
for(re int i=0; i<ans.size(); i++)
{ printf("%c",ans[i]); }
printf("\n");
}
return 0;
};
}
signed main()
{ return OMA::main(); }
T3
考场不会....
正解:
是根本没有想到的最短路....
先咕了.....
对于一条边 \(u,v,l,r\),如果 \(u\) 是没有学会的那个,那么最优的就应该是 \(u\) 和 \(v\) 在 \(l\) 吃了饭,\(v\) 在 \(l+1\) 学会了毒瘤算法。
设 \(lim_{i}\) 表示只考虑-1的限制,\(i\) 最早学会毒瘤算法的时间。
先以所有最后没学会毒瘤算法的为源点,跑遍多源最短路,算出 \(lim\) 数组,那么对于 \(lim\) 的转移:
再以1为源点跑遍最短路,算出 \(dis\) 数组,表示在满足条件下 \(u\) 最早能学会的时间。
设 \(tmp=\max(l,\max(lim_{u},lim_{v}))\) ,则对于 \(dis\) 的转移:
判断矛盾就枚举一下每个人,看是否有最后学会了的,但是其 \(dis\) 没有被更新的,还有一个比较草蛋的,注意一下1直接跟没有学会的恰饭,也是不合法的。
最后如果是有必要举行的午餐,答案则为 \(\max(l,\max(dis_{u},dis_{v}))\) ,不必要就随便输出一个 \([l,r]\) 之内的数即可,然而spj挂了,没必要的只有输出 \(l\) 才行。
Code
#include<queue>
#include<cstdio>
#include<cctype>
#include<cstdlib>
#define MAX 200003
#define re register
const int INF = 1e9+3;
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;
auto line = []() { printf("\n"); };
auto debug = []() { printf("fuck\n"); };
auto begin = []() { printf("begin\n"); }; auto end = []() { printf("end\n"); };
auto max = [](int a,int b) -> int { return a>b?a:b; };
}using namespace some;
namespace SSSP
{
struct graph
{
int next;
int from;
int to;
int l,r;
}edge[MAX<<1];
int cnt=1,head[MAX];
queue<int>q;
bool vis[MAX];
int lim[MAX],dis[MAX];
auto add = [](int u,int v,int l,int r) -> void { edge[++cnt] = (graph){head[u],u,v,l,r},head[u] = cnt; };
auto spfa1 = []() -> void
{
while(!q.empty())
{
//begin();
int u = q.front(); q.pop(); vis[u] = false;
//printf("u=%d\n",u);
for(re int i=head[u],v,l,r; i; i=edge[i].next)
{
v = edge[i].to,l = edge[i].l,r = edge[i].r;
if(lim[u]>r&&lim[v]<l+1)
{
//printf("v=%d\n",v);
lim[v] = l+1;
if(!vis[v])
{ q.push(v); vis[v] = true; }
}
}
//end();
}
};
auto spfa2 = []() -> void
{
q.push(1); dis[1] = 0,vis[1] = true;
while(!q.empty())
{
//debug(); begin();
int u = q.front(); q.pop(); vis[u] = false;
//printf("u=%d\n",u);
for(re int i=head[u],v,l,r,tmp; i; i=edge[i].next)
{
v = edge[i].to,l = edge[i].l,r = edge[i].r;
tmp = max(l,max(lim[v],dis[u]));
if(tmp<=r&&dis[v]>tmp)
{
//printf("v=%d\n",v);
dis[v] = tmp;
if(!vis[v])
{ q.push(v); vis[v] = true; }
}
}
//end();
}
};
}using namespace SSSP;
namespace OMA
{
int n,m,sta[MAX];
auto main = []() -> signed
{
freopen("lunch.in","r",stdin); freopen("lunch.out","w",stdout);
cin >> n >> m;
for(re int i=1,u,v,l,r; i<=m; i++)
{ cin >> u >> v >> l >> r; add(u,v,l,r),add(v,u,l,r); }
for(re int i=1; i<=n; i++)
{ cin >> sta[i]; dis[i] = INF; if(sta[i]==-1){ q.push(i); lim[i] = INF,vis[i] = true; } }
spfa1(),spfa2();
//begin(); for(re int i=1; i<=n; i++) { printf("%d\n",lim[i]); } end();
//begin(); for(re int i=1; i<=n; i++) { printf("%d\n",dis[i]); } end();
if(lim[1])
{ printf("Impossible\n"); return 0; }
for(re int i=1; i<=n; i++)
{ if(sta[i]==1&&dis[i]>=INF){ printf("Impossible\n"); return 0; } }
for(re int i=2; i<=cnt; i+=2)
{
if(dis[edge[i].from]>edge[i].r||dis[edge[i].to]>edge[i].r)
{ printf("%d\n",edge[i].l); }
else
{ printf("%d\n",max(edge[i].l,max(dis[edge[i].from],dis[edge[i].to]))); }
}
return 0;
};
}
signed main()
{ return OMA::main(); }
T4
考场不会....
正解:
我觉得rvalue学长写的挺好的....
Code
#include<cstdio>
#include<climits>
#include<cstring>
#define MAX 403
#define INF INT_MAX
#define re register
#define int long long
const int mod = 1e9+7;
namespace some
{
struct stream
{
template<typename type>inline stream &operator >>(type &s)
{
int w=1; s=0; char ch=getchar();
while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
return s*=w,*this;
}
}cin;
auto abs = [](int a) -> int { return a>=0?a:-a; };
auto max = [](int a,int b) -> int { return a>b?a:b; };
auto min = [](int a,int b) -> int { return a<b?a:b; };
auto swap = [](int &a,int &b) -> void { int t=a; a=b; b=t; };
auto fd = [](int a,int b) -> int { return b-mod+a>=0?b-mod+a:a+b; };
}using namespace some;
namespace OMA
{
int t,n,m;
int l[MAX],r[MAX];
int dp[MAX][MAX];
auto main = []() -> signed
{
freopen("count.in","r",stdin);
freopen("count.out","w",stdout);
cin >> t;
while(t--)
{
cin >> n >> m;
memset(dp,0,sizeof(dp));
for(re int u=1; u<=n; u++)
{ l[u] = 0,r[u] = INF; }
for(re int i=1,u,v; i<=m; i++)
{
cin >> u >> v;
if(u<v)
{ r[u] = min(r[u],v-u-1); }
else
{ l[v] = max(l[v],u-v); }
}
for(re int i=n; i; i--)
{
if(!l[i])
{ dp[i][1] = 1; }
for(re int j=l[i]+1; j<=n-i+1; j++)
{
int edge = min(r[i],j-1);
if(!l[i])
{ dp[i][j] = fd(dp[i][j],dp[i+1][j-1]); }
if(edge==j-1)
{ dp[i][j] = fd(dp[i][j],dp[i+1][j-1]); }
for(re int k=l[i]; k<=edge; k++)
{ dp[i][j] = fd(dp[i][j],dp[i+1][k]*dp[i+1+k][j-k-1]%mod); }
}
}
printf("%lld\n",dp[1][n]);
}
return 0;
};
}
signed main()
{ return OMA::main(); }