【长沙集训】2017.9.12
并不怎么傻逼的题也把自己考成傻逼。大概是全机房最后几个改完题的人了。。QAQ
T1 APIO2009抢掠计划 // BZOJ1197: [Apio2009]ATM
好像是之前哪位学长讲过,tarjan缩点,然后值取反跑spfa或者拓扑排序后做Dp;考场上(第一次)尝试拓扑后DP,然后十分SB地一开始只放进了起点,认为其余入度为0的点无所谓(能过那么多点也是神奇)。实际上显然需要把所有入读为0的点放入栈中,dp值初始为最大,起点为0,然后一边拓扑一边dp;
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
#include<stack>
using namespace std;
const int maxn=500000+299;
int s,p,n,m,x,y,a[maxn],is[maxn],fi[maxn],nx[maxn],tt[maxn],ecnt,fir[maxn],nxt[maxn],to[maxn];
int dfs_clock,dfn[maxn],low[maxn],num[maxn],val[maxn],in[maxn],tot,e,f[maxn],ok[maxn];
void add(int x,int y) {
nx[++ecnt]=fi[x]; fi[x]=ecnt; tt[ecnt]=y;
}
void Add(int x,int y) {
nxt[++e]=fir[x]; fir[x]=e; to[e]=y; in[y]++;
}
void init(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++) {
scanf("%d%d",&x,&y);
add(x,y);
}
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
scanf("%d%d",&s,&p);
for(int i=1;i<=p;i++) {
scanf("%d",&x);
is[x]=1;
}
}
stack<int>sta;
void tarjan(int x) {
dfn[x]=low[x]=++dfs_clock;
sta.push(x);
for(int i=fi[x];i;i=nx[i]) {
if(!dfn[tt[i]]) {
tarjan(tt[i]);
low[x]=min(low[x],low[tt[i]]);
}
else if(!num[tt[i]]) low[x]=min(low[x],dfn[tt[i]]);
}
if(dfn[x]==low[x]) {
tot++;
for(;;){
int u=sta.top();
sta.pop();
num[u]=tot;
val[tot]+=a[u];
if(is[u]) ok[tot]=1;
if(u==x) break;
}
}
}
stack<int>ss;
void dp() {
for(int i=1;i<=tot;i++)
if(!in[i]) ss.push(i);
memset(f,128,sizeof(f));
f[num[s]]=val[num[s]];
while(!ss.empty()) {
int v=ss.top();
ss.pop();
for(int i=fir[v];i;i=nxt[i]) {
int u=to[i];
f[u]=max(f[u],f[v]+val[u]);
in[u]--;
if(!in[u]) ss.push(u);
}
}
}
void work() {
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++) {
for(int j=fi[i];j;j=nx[j]) {
if(num[i]!=num[tt[j]])
Add(num[i],num[tt[j]]);
}
}
dp();
int ans=0;
for(int i=1;i<=tot;i++)
if(ok[i]) ans=max(ans,f[i]);
printf("%d\n",ans);
}
int main() {
freopen("atm.in","r",stdin);
freopen("atm.out","w",stdout);
init();
work();
return 0;
}
一串字符你可以把它k个化成一份问最多可以得到多少种本质不同的串,正反颠倒相同算同一种。
一看是LLJ大佬曾经讲过的时间为nlogn暴力可过,于是直接哈希,正着哈希一遍反着一遍,暴力跑答案。
一个剪枝,k小于等于n/目前的最大ans,不过好像没太大用。
考场上第一次自己写哈希,然后乱搞了个不知道什么,还写的双哈希,可能也是因为(数)双(据)哈(太)希(弱)让垃圾算法过了一些点
回来抄了一份大佬的代码,才知道哦哈希是这么搞的,不需要求逆元来除(mdzz)而是乘和减,也不需要开数组来存(给自己跪了),最后sort一遍。
大佬的算法比较好的一点是正反存一个结构体,然后据说这样和双哈希效果差不多冲突可能性很小了。
自己的乱搞哈希似乎可以过考试的辣鸡数据,但是不小心一个数组越界了就GG。。。
//Twenty #include<cstdio> #include<cstdlib> #include<iostream> #include<algorithm> #include<cmath> #include<cstring> #include<queue> #include<vector> using namespace std; typedef long long LL; const int maxn=2e5+299; const int N=1e7+5; const int mod=1e9+7; LL ti,n,ans,tot,a[maxn],bo[maxn],an[maxn],h[maxn],hf[maxn],base=3;//131;//!!!!!!! int cnt,tpcnt; struct node{ LL z,f; friend bool operator <(const node &a,const node&b) { return a.z<b.z||(a.z==b.z&&a.f<b.f); } }p[maxn]; LL ksm(int a,int b,int mod) { LL res=1,base=a; while(b) { if(b&1) (res*=base)%=mod; (base*=base)%=mod; b>>=1; } return (int)res; } void has() { h[n]=a[n]; for(int i=n-1;i>=1;i--) h[i]=(a[i]+h[i+1]*base%mod)%mod; hf[1]=a[1]; for(int i=2;i<=n;i++) hf[i]=(a[i]+hf[i-1]*base%mod)%mod; } void getha(int l,int r,int &z,int &f) { z=h[l]; if(r<n) ((z-=h[r+1]*ksm(base,r-l+1,mod)%mod)+=mod)%=mod; f=hf[r]; if(l>1) ((f-=hf[l-1]*ksm(base,r-l+1,mod)%mod)+=mod)%=mod; } int work(int k) { int d=n/k,res=0; tpcnt=0; for(int i=1;i+k-1<=n;i+=k) { int tmp1,tmp2; getha(i,i+k-1,tmp1,tmp2); if(tmp1>tmp2) swap(tmp1,tmp2); p[++tpcnt].z =tmp1; p[tpcnt].f=tmp2; } sort(p+1,p+tpcnt+1); for(int i=1;i<=tpcnt;i++) { if(p[i].z!=p[i-1].z||p[i].f!=p[i-1].f) res++; } return res; } int main() { freopen("beads.in","r",stdin); freopen("beads.out","w",stdout); scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); if(!bo[a[i]]) tot++; bo[a[i]]=1; } if(tot==1) { printf("1 "); cout<<n<<endl; for(int i=1;i<n;i++) printf("%d ",i); printf("%d\n",n); return 0; } ans=tot; an[cnt=1]=1; has(); for(ti=2;ti<=n/ans;ti++) { int now=work(ti); if(now>ans) ans=now,an[cnt=1]=ti; else if(now==ans) an[++cnt]=ti; } //printf("%d %d\n",ans,cnt); cout<<ans<<" "<<cnt<<endl; for(int i=1;i<cnt;i++) printf("%d ",an[i]); printf("%d\n",an[cnt]); return 0; } /* 21 1 1 1 2 2 2 3 3 3 1 2 3 3 1 2 2 1 3 3 2 1 6 1 2 2 1 2 2 */
T3 Clever
一道水题。随便建边跑spfa,考场上没看到是双向边,而且建向下落的边时开了个结构体存sort了一遍,写法十分毒瘤,也因此后来的spfa和Sort后的点弄混了,就GG。能过这么多点也是不容易。
正解直接暴力问一遍可以连边不就好了啊。。。
//Twenty #include<cstdio> #include<cstdlib> #include<iostream> #include<algorithm> #include<cmath> #include<cstring> #include<queue> #include<vector> using namespace std; typedef long long LL; const int maxn=100+10; const int maxm=1e6+299; int n,f[maxn],fir[maxn],nxt[maxm],to[maxm],ecnt,que[maxn]; double val[maxm],ans=1e9+7,v; struct node{ double x,y; int id,f; friend bool operator <(const node &a,const node&b){ return a.x<b.x||(a.x==b.x&&a.y<b.y); } }p[maxn]; void add(int u,int v,double w){ nxt[++ecnt]=fir[u]; fir[u]=ecnt; to[ecnt]=v; val[ecnt]=w; } double pf(double x) {return x*x;} double cal(int x,int y){ return (sqrt(pf(p[x].x-p[y].x)+pf(p[x].y-p[y].y)))/v; } double C(int x,int y){ return sqrt(fabs(p[y].y-p[x].y)*2.0/10.0); } double dis[maxn],vis[maxn]; void spfa(){ queue<int>que; for(int i=1;i<=n;i++) dis[i]=1e9+7,vis[i]=0; vis[1]=1; dis[1]=0; que.push(1); while(!que.empty()) { int now=que.front(); que.pop(); vis[now]=0; for(int i=fir[now];i;i=nxt[i]) { if(dis[to[i]]>dis[now]+val[i]){ dis[to[i]]=dis[now]+val[i]; if(!vis[to[i]]) { vis[to[i]]=1; que.push(to[i]); } } } } ans=dis[n]; } int main() { freopen("clever.in","r",stdin); freopen("clever.out","w",stdout); scanf("%d%lf",&n,&v); for(int i=1;i<=n;i++) { scanf("%lf%lf%d",&p[i].x,&p[i].y,&p[i].f); if(i==58) { int debug=1; } add(p[i].f,i,cal(i,p[i].f)); add(i,p[i].f,cal(i,p[i].f)); } for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) if(p[i].x==p[j].x) { int x,y;double z; if(p[i].y>p[j].y) x=i,y=j; else x=j,y=i; z=C(i,j); add(x,y,z); } spfa(); printf("%.2lf\n",ans); return 0; } /* 9 1 5 0 0 5 5 1 6 5 2 7 6 2 6 9 2 3 6 2 4 5 2 3 2 7 7 2 3 */
T4 逛公园
应该是最有意思的题了。考场上T2自己YY的乱搞hash调了太久没时间写了,就打了输出1还有9分。。。
放一下题
背景
SC theme Park 终于开业了,可爱的平平小朋友很荣幸的成为第一个游客。
公园设计强调,复杂就是美。scp大老板给他的公园设计了一个极其复杂的布局:
由于公园极大,而景点又很多,scp大老板在任意的两个景点之间都建造了一条星光小道,而且还为每条小道制定了方向。
题目描述
现在,平平从scp大老板那里得知公园总共有N个景点,并且已经知道了每一条星光小道的方向,但由于平平的方向感极差而RP又极低,于是一旦公园中出现回路,即存在环,平平便会迷路,并且无论怎么走都走不出去。
这样,scp大老板可就伤透脑筋了。为了使平平不会迷路,scp大老板决定改变其中M条星光小道的方向使得公园里不存在回路,但scp大老板又希望改变的小道的条数最少。由于很忙,腾不出时间,scp大老板只好请教即将参加noip的你。(注意:任意两个景点之间有且只有一条星光小道,且任意两条小道都是不相通的,即不能从一条小道不经过景点直接到达另一条小道)。
输入
第一行有一个整数N,表示有N个景点。
接下来是一张N*N的矩阵,第i+1行第j列表示有无从景点i指向景点j的星光小道(0表示没有,1表示有)。
输出
输出仅包括一行,即M的最小值。
输入样例 ( park.in)
4
0 0 0 0
1 0 1 0
1 0 0 1
1 1 0 0
输出样例 ( park.out)
1
数据规模
对于30%数据,1<=N<=10;
对于100%数据,1<=N<=20.
做法还是比较多,题解给的是搜索然而并没有人用搜索做出来?
只有自己看的博客放一下别人的题解好像没什么毛病?
任意两点间都有一条边,而且是有向的,对于这样的有向完全图不存在环当且仅当所有顶点的出度从小到大排列依次为0, 1, 2, ... , n-1,下面我们给出证明:
如果一个有向图的所有点出度都至少为1,那么这个图一定有环,因为在找到环之前DFS总可以找到新的节点。如果有向图无环,必然存在一个点没有出度。由于任两点之间都有有向边,那么其它所有点都要连一条边指向它,这样其它所有点的出度都至少为1了。删掉这个出度为0的点后剩下的图仍然无环,不断对剩下的图继续上面的过程就得到了我们的结论。
我们有了这个结论之后就有很多做法了。
1、搜索,题解说,这样搜索就比较显然了。然后写了个暴力迭代加深,大概只能过30...不知道如何优化
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
int n,a[50][50],ans,out[50],ecnt,c[50],tp[50],que[500],vis[50],tot,lim;
int dfs(int cnt,int x,int num,int lim) {
if(num==n-1&&out[x]==num) return 1;
if(!x||out[x]==num) {
for(int i=1;i<=n;i++) {
if(!vis[i]) {
vis[i]=1;
if(dfs(cnt,i,num+1,lim)) return 1;
vis[i]=0;
}
}
return 0;
}
for(int i=1;i<=n;i++) {
if(a[x][i]&&!vis[i]) {
int now=abs(out[x]-1-num),tpc=0;
a[x][i]=0;
a[i][x]=1;
out[x]--;
out[i]++;
for(int j=0;j<=n;j++) c[j]=0;
for(int j=1;j<=n;j++) if(!vis[j]) c[out[j]]++;
for(int j=2;j<=n;j++)
c[j]+=c[j-1];
for(int j=1;j<=n;j++) if(!vis[j]) {tp[c[out[j]]--]=j,tpc++;}
for(int j=1;j<=tpc;j++) {
now+=abs((num+j)-out[tp[j]]);
}
if(cnt+1+now/2<=lim) {
if(dfs(cnt+1,x,num,lim)) return 1;
}
a[x][i]=1;
a[i][x]=0;
out[x]++;
out[i]--;
}
}
return 0;
}
int main() {
//freopen("park.in","r",stdin);
//freopen("park.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) {
scanf("%d",&a[i][j]);
if(a[i][j]) {
out[i]++;
}
}
for(lim=1;lim;lim++) {
int debug=1;
if(dfs(0,0,-1,lim))
break;
}
//printf("%d\n",lim);
cout<<lim;
return 0;
}
2.模拟退火
LLJ大佬的做法
实在太强啦orz orz
似乎没什么可说的,就跑模拟退火,应该是可以过的。
然后自己之前的板子可能有点问题,非常不稳,换了LLJ大佬的写法之后就可以过了。
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
#include<ctime>
using namespace std;
const double T=1e5;
int n,a[50][50],ans,out[50],p[50];
int solve() {
int res=0;
random_shuffle(p+1,p+n+1);
for(int i=1;i<=n;i++) {
for(int j=i+1;j<=n;j++) {
if(!a[p[j]][p[i]]) res++;
}
}
double t=T;
while(t>0.1){
int x=rand()%n+1,y=rand()%n+1,now=0;
swap(p[x],p[y]);
for(int i=1;i<=n;i++) {
for(int j=i+1;j<=n;j++) {
if(!a[p[j]][p[i]]) now++;
}
}
if(now<res||rand()<=exp((res-now)/t)*RAND_MAX)
res=now;
else swap(p[x],p[y]);
t*=0.999;
}
return res;
}
int main() {
//freopen("park.in","r",stdin);
//freopen("park.out","w",stdout);
srand(time(0));
scanf("%d",&n);
for(int i=1;i<=n;i++) {
p[i]=i;
for(int j=1;j<=n;j++) {
scanf("%d",&a[i][j]);
if(a[i][j]) {
out[i]++;
}
}
}
ans=solve();
for(int ti=1;ti<=50;ti++) {
ans=min(ans,solve());
}
printf("%d\n",ans);
return 0;
}
3.状压dp
长沙学长讲的做法,也是SXY大佬的做法。
非常妙啊,十分简洁方便,得出上面的结论后就可以直接跑了,看代码吧。
//Twenty
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
int n,a[50][50],out[50],dp[1<<20],now,que[50],tot;
int main() {
freopen("park.in","r",stdin);
freopen("park.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) scanf("%d",&a[i][j]);
int N=(1<<n)-1;
memset(dp,127,sizeof(dp));
dp[0]=0;
for(int i=0;i<N;i++){
now=0; tot=0;
for(int j=1;j<=n;j++) {
if((i&(1<<j-1))) ;
else que[++tot]=j;
}
for(int j=1;j<=tot;j++) {
now=0;
for(int k=1;k<=tot;k++)
if(j!=k&&!a[que[k]][que[j]])
now++;
int x=i|(1<<que[j]-1);
dp[x]=min(dp[x],dp[i]+now);
}
}
printf("%d\n",dp[N]);
return 0;
}
总结:
前三题比较简单,但考场上犯了各种各样的错误,其实只是过了样例的程序没爆0就已经超过自己的预料了,码力还是太弱了。
然后t2是本身有点问题,算是学到了新东西。
t4没有时间写比较遗憾,应该是全场最有价值的题吧?当时瞄了一眼有往模拟退火去想,但是没有去找结论,也是没时间,但给时间也不一定能找到,毕竟非常不擅长推结论,只能尽量多见一点题增长下见识吧。