1. 程式人生 > >【長沙集訓】2017.9.12

【長沙集訓】2017.9.12

print 一道 要求 esp 做出 cnblogs 多點 輸出 family

並不怎麽傻逼的題也把自己考成傻逼。大概是全機房最後幾個改完題的人了。。QAQ

T1 APIO2009搶掠計劃

好像是之前哪位學長講過,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; }
View Code

T2 BZOJ 2081 beads

一串字符你可以把它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
*/
View Code

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
*/
View Code

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;
}
View Code

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;
}
View Code

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;
}
View Code

總結:

前三題比較簡單,但考場上犯了各種各樣的錯誤,其實只是過了樣例的程序沒爆0就已經超過自己的預料了,碼力還是太弱了。

然後t2是本身有點問題,算是學到了新東西。

t4沒有時間寫比較遺憾,應該是全場最有價值的題吧?當時瞄了一眼有往模擬退火去想,但是沒有去找結論,也是沒時間,但給時間也不一定能找到,畢竟非常不擅長推結論,只能盡量多見一點題增長下見識吧。

【長沙集訓】2017.9.12