雅禮學習10.3

雅禮學習10.3c++

各題情況

T1

暴力修改+一維差分+二維差分算法

莫名其妙就沒了49分。。。數組

好像是數組開的不夠大?學習

T2

這。。。機率和指望,一會不會,連那個一分的部分分都沒有任何思路優化

T3

題目並無看太懂。。spa

寫了一個枚舉算法,而後對某個一分的數據輸出顯然的結果code

。。。blog

而後就只拿了1分排序

枚舉掛了,由於會錯了題目含義遞歸

題目及考場代碼

T1

圖片.png

圖片.png

/*
 * 一個個修改確定超時。。
 * q==0的直接輸出0
 * 19分應該是暴力
 *
 * 考慮對每次操做,計算一共修改了多少個位置
 * 奇數個的話就讓當前答案異或這個數字
 * 對於邊界單獨討論
 * 可是問題在於。。。若是修改的位置恰好把當前答案所在位置變大了,就不該該是異或,而是+
 * 真的不會維護啊
 * 僞裝這麼寫是正確的吧。。。
 *
 * 每行差分
 * 最後O(n^2)統計
 */
#include <cstdio>

inline int read()
{
    int n=0,w=1;register char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
    while(c>='0'&&c<='9')n=n*10+c-'0',c=getchar();
    return n*w;
}
inline int min(int x,int y)
{return x<y?x:y;}

const int N=1002;
long long map[N][N],add[N][N],cut[N][N];

int main()
{
    freopen("u.in","r",stdin);
    freopen("u.out","w",stdout);
    int n=read(),q=read(),r,c,s,l,x;
    long long ans=0;
    if(q==0)
    {
        printf("0");
        goto E;
    }
    if(q<=400)
    {
        while(q--)
        {
            r=read(),c=read(),l=read(),s=read();
            x=min(r+l-1,n);
            for(int i=r;i<=x;++i)
                for(int j=c;j<=min(i-r+c,n);++j)
                    map[i][j]+=s;
        }
        for(int i=1;i<=n;++i)
            for(int j=1;j<=n;++j)
                ans^=map[i][j];
    }
    else
        if(q<=2000)
        {
            while(q--)
            {
                r=read(),c=read(),l=read(),s=read();
                x=min(r+l-1,n);
                for(int i=r;i<=x;++i)
                    map[i][c]+=s,map[i][min(i-r+c,n)+1]+=-s;
            }
            for(int i=1;i<=n;++i)
            {
                x=0;
                for(int j=1;j<=n;++j)
                {
                    x+=map[i][j];
                    printf("%d ",x);
                    ans^=x;
                }
                puts("");
            }
        }
        else
        {
            while(q--)
            {
                r=read(),c=read(),l=read(),s=read();
                add[r][c]+=s,add[r+l][c]-=s;
                cut[r][c+1]+=s,cut[r+l][c+l+1]-=s;
            }
            for(int i=1;i<=n;++i)
                for(int j=1;j<=n;++j)
                {
                    add[i][j]+=add[i-1][j];
                    cut[i][j]+=cut[i-1][j-1];
                }
            for(int i=1;i<=n;++i)
                for(int j=1;j<=n;++j)
                {
                    map[i][j]+=map[i][j-1]+add[i][j]-cut[i][j];
                    ans^=map[i][j];
                }
        }
            
    /*
    else
    {
        while(q--)
        {
            r=read(),c=read(),l=read(),s=read();
            x=l+1;
            if(r+l-1>n || c+l-1>n)
            {//想了半天沒想出來怎麼快速把多餘的部分刪掉。
                x=0;
                for(int i=r;i<=min(r+l-1,n);++i)
                    x+=min(i-r+c,n);
            }
            if(x&1)ans^=s;
        }
    }*/
    printf("%lld",ans);
            
E:  fclose(stdin);fclose(stdout);
    return 0;
}

T2

圖片.png

圖片.png

/*
 * 機率。。。
 * 根本一會不會啊。。。
 * 騙分走人
 */
#include <cstring>
#include <cstdio>

const int N=31;
char s[N];

int main()
{
    freopen("v.in","r",stdin);
    freopen("v.out","w",stdout);

    scanf("%d%d",&n,&k);
    scanf("%s",s);
    int x=0;
    for(int i=0;i<n;++i)
        if(s[i]=='W')
            ++x;
    if(k==0 || k==n)
        printf("%.10lf",(double)1.0*k);
    else    printf("%.10lf",(double)x*1.0/2);

    fclose(stdin);fclose(stdout);
    return 0;
}

T3

圖片.png

圖片.png

/*
 * 輸出0有1分。
 */
#include <cstdio>
#include <vector>

int n,step,cnt,k_2,z_2,ku;
int l_note[100023];
struct note{
    int con,have,wash;
};
bool dis[100023];
std::vector<note> tree[100023];
std::vector<int> kuai[100023];
void search(int num)
{
    for(int space,i=0;i<l_note[num];i++)
    {
        space=tree[num][i].con;
        if(((tree[num][i].wash!=tree[num][i].have)||(tree[num][i].wash==2))&&dis[space]!=true)
        {
            tree[num][i].have=tree[num][i].wash;
            ++cnt;
            dis[num]=true;
            search(space);
            break;
        }
    }
}
bool vis[100023];
void qk(int num)
{
    vis[num]=true;
    for (int i=0;i<kuai[num].size();++i)
    {
        int space=kuai[num][i];
        if(tree[num][i].wash==2)++k_2;
        if(vis[space]!=true)
            qk(space);
    }
}
int main ()
{
    freopen("w.in","r",stdin);
    freopen("w.out","w",stdout);
    scanf("%d",&n);
    if(n<=1000)
    {
        for(int a,b,c,d,i=1;i<n;++i)
        {
            scanf("%d%d%d%d",&a,&b,&c,&d);
            tree[a].push_back((note){b,c,d});
            ++l_note[a];
            tree[b].push_back((note){a,c,d});
            ++l_note[b];
            if((c!=d)||(d==2))
            {
                kuai[a].push_back(b);
                kuai[b].push_back(a);
            }
            if(d==2)++z_2;
        }
        for(int i=1;i<=n;++i)
        {
            for(int o=0;o<l_note[i];o++)
            {
                if((tree[i][o].wash!=tree[i][o].have)&&tree[i][o].wash!=2)
                {
                    dis[tree[i][o].con]=true;
                    search(i);
                    break;
                }
            }
        }
        for(int i=1;i<=n;++i)
            if(vis[i]!=true&&kuai[i].size()!=0)
            {
                ++ku;
                qk(i);
            }
        step=ku-(z_2-k_2/2);
        printf("%d %d",step,cnt);
    }
    else    printf("0 0");
    fclose(stdin);fclose(stdout);
    return 0;
}

正解

T1

第一眼\(n\times q\)作法,\(3e8\)顯然不能過

那麼考慮對其進行優化:差分序列的訪問時不連續的,因此咱們考慮把它變成連續的

就過了。。。

官方正解:

豎着作一次差分,斜着作一次差分,那麼一個三角形就能夠肯定出來了,然後\(n^2\)掃一遍求異或和

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn=1e3+10;
int n,q;
ll a[maxn][maxn],b[maxn][maxn],ans;

inline void add_a(int x,int y,int v){
    if(x<=n&&y<=n)
        a[x][y]+=v;
}
inline void add_b(int x,int y,int v){
    if(x<=n&&y<=n)
        b[x][y]+=v;
}

int main(){
    freopen("u.in","r",stdin);
    freopen("u.out","w",stdout);
    scanf("%d%d",&n,&q);
    while(q--){
        int r,c,l,s;
        scanf("%d%d%d%d",&r,&c,&l,&s);
        add_a(r,c,s);
        add_a(r+l,c+l,-s);
        add_b(r+l,c,-s);
        add_b(r+l,c+l,s);
    }
    for(int i=1;i<=n;++i)
        for(int j=1;j<=n;++j){
            if(i>1)
                a[i][j]+=a[i-1][j-1]+a[i-1][j]-a[i-2][j-1];
            else
                a[i][j]+=a[i-1][j-1]+a[i-1][j];
            b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
            ans^=a[i][j]+b[i][j];
        }
    printf("%lld\n",ans);
    return 0;
}

T2

能夠獲得一個\(O(2^n\times n)\)的狀壓\(DP\)的作法,記錄每一個球是否尚未被移除,而後按照最優策略指望移除白球數

事實上有不少重複狀態,也就是剩下的求的顏色序列相同時結果是同樣的

考慮將狀態記成剩下的顏色序列,長度較小的時候就直接用數組去存,較大的時候用\(map\)去存

狀態個數能獲得一個上界是\(\sum_{i=0}^n\min\{2^i,{i\choose n}\}\)(事實上最大值爲\(\sum_{i=1}^{n+1}Fib_i\)可是這一點也不\(noip\)

#include<bits/stdc++.h>
using namespace std;

const int maxn=30+5;
int n,k;
char s[maxn];

namespace ${
    const int xxx=24;
    double a[1<<xxx+1];
    map<int,double> m[maxn];
    inline void init(){
        for(int i=0;i<1<<xxx+1;++i)
            a[i]=-1;
    }
    inline bool count(int bit,int len){
        if(len<=xxx)
            return a[1<<len|bit]!=-1;
        else
            return m[len].count(bit);
    }
    inline double&find(int bit,int len){
        if(len<=xxx)
            return a[1<<len|bit];
        else
            return m[len][bit];
    }
}
inline int erase(int bit,int k){
    return bit&(1<<k)-1|bit>>1&-1<<k;
}
inline double max_(double a,double b){
    return a>=b?a:b;
}
double dfs(int bit,int len){
    if(len<=k)
        return 0;
    if($::count(bit,len))
        return $::find(bit,len);
    double&res=$::find(bit,len);
    res=0;
    for(int i=0,j=len-1;i<=j;++i,--j)
        if(i<j)
            res+=max_(dfs(erase(bit,i),len-1)+(bit>>i&1),dfs(erase(bit,j),len-1)+(bit>>j&1))*2;
        else
            res+=dfs(erase(bit,i),len-1)+(bit>>i&1);
    return res/=len;
}

int main(){
    freopen("v.in","r",stdin);
    freopen("v.out","w",stdout);
    $::init();
    scanf("%d%d%s",&n,&k,s);
    k=n-k;
    int bit=0;
    for(int i=0;i<n;++i)
        bit|=(s[i]=='W')<<i;
    printf("%.10f\n",dfs(bit,n));
    return 0;
}

T3

若是最後翻轉的邊集是\(S\),最少操做數爲\(\{V,S\}\)中奇數度數的點的一半,最小操做總長度是\(|S|\)

考慮樹形\(DP\)\(dp[i][0/1]\)記錄以\(i\)爲根的子樹內,\(i\)與父親之間的邊是否翻轉,最少的奇數度數的點的個數,此時的最小總長度

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e5+10;
const pair<int,int> inf=make_pair(1e9,1e9);
int n;
vector<pair<int,int> > g[maxn];
pair<int,int> dp[maxn][2];

inline pair<int,int> operator+ (pair<int,int> a,pair<int,int> b){
    return make_pair(a.first+b.first,a.second+b.second);
}
void dfs(int pos,int fa,int type){
    pair<int,int> tmp0(0,0),tmp1(inf);
    for(int i=0,v;i<g[pos].size();++i)
        if((v=g[pos][i].first)!=fa){
            dfs(v,pos,g[pos][i].second);
            pair<int,int> nxt0,nxt1;
            nxt0=min(tmp0+dp[v][0],tmp1+dp[v][1]);
            nxt1=min(tmp1+dp[v][0],tmp0+dp[v][1]);
            tmp0=nxt0;tmp1=nxt1;
        }
    if(type==0||type==2)
        dp[pos][0]=min(tmp0,make_pair(tmp1.first+1,tmp1.second));
    else
        dp[pos][0]=inf;
    if(type==1||type==2)
        dp[pos][1]=min(make_pair(tmp0.first+1,tmp0.second+1),make_pair(tmp1.first,tmp1.second+1));
    else
        dp[pos][1]=inf;
}

int main(){
    freopen("w.in","r",stdin);
    freopen("w.out","w",stdout);
    scanf("%d",&n);
    for(int i=1,a,b,c,d;i<n;++i){
        scanf("%d%d%d%d",&a,&b,&c,&d);
        if(d!=2)
            d=(c!=d);
        g[a].push_back(make_pair(b,d));
        g[b].push_back(make_pair(a,d));
    }
    dfs(1,0,0);
    printf("%d %d\n",dp[1][0].first/2,dp[1][0].second);
    return 0;
}

下午講課:搜索與剪枝

主要是題目只有題目

例一

有一個\([1,2^n]\)的排列\(A\{1,\cdots,2^n\}\)

能夠執行的操做有\(n\)種,每種操做最多能夠執行一次。第\(i\)種操做:將序列從左到右劃分爲\(2^{n-i+1}\)段,每段剛好包括\(2^{i-1}\)個數,而後總體交換其中兩段。
求能夠將數組\(A\)從小到大排序的不一樣的操做序列有多少個。
兩個操做序列不一樣,當且僅當操做個數不一樣,或者至少一個操做不一樣(種類不一樣或者操做位置不一樣)。

\(n\le 12\)

解:

首先,任意一個合法的操做序列,咱們能夠改變其順序,依然知足條件。
那麼這一類的操做序列的貢獻,即爲操做次數的階乘。
那麼,如今只考慮種類編號遞增的操做序列。第\(i\)種操做時,序列分紅了大小爲\(2^{i-1}\)的段,若是某個段不是遞增且連續的,那麼最後確定不會知足條件。因此,在這種操做考慮完後,每一個大小爲\(2^i\)的段應當遞增且連續。
當考慮第\(i\)種操做時:

  • 若是不合條件的\(2^i\)大小的段超過\(2\)個,直接退出
  • 若是不存在的話,直接繼續
  • 若是隻有\(1\)個,交換其包含的\(2^{i-1}\)的兩段,判斷是否可行
  • 若是有\(2\)個就分別搜索\(4\)種交換方案

例二

農夫約翰須要一些特定規格的木材(共\(n\)塊,長度不必定相同),但是他只剩下一些大規格的木板(共\(m\)塊,長度不必定相同)。不過約翰能夠將這些木板切割成他所須要的規格。
求約翰最多可以獲得多少他所須要的木材。

\(n\le 1000,m\le 50,lenth\le 32767\)

解:

顯然能夠二分答案。
判斷答案爲\(k\)是否可行,顯然能夠將木材排序,搜索最短的k 塊木材是否
能獲得。
而後剪枝:

  • 從小到大搜索所需的木材從哪塊木板獲得,由於長的木材限制比較大
  • 若是兩塊所需木材長度相同,後搜索的那一塊的來源只須要從前者的來源開始枚舉
  • 當一塊原料的長度比最短的需求還短,那麼直接丟棄,若是丟棄總量+所需總量\(\gt\)原料總量,剪掉

例三

給出一個數字\(S\),輸出全部約數和等於\(S\)的數

一共\(T\)次詢問

\(S\le 2e9,T\le 100\)

解:

\(n=\prod_i p_i^{a^i}\Rightarrow \sigma(n)=\prod_i\sum_{j=0}^{a_i}p_i^j\)

那麼咱們能夠經過枚舉\(p_i\)及其\(a_i\)來搜索

若當前須要獲得的\(S\)能夠表示爲爲一個未搜索過的質數與\(1\)的和,那麼以前的數與這個質數的乘積是一個合法答案

對於每一個使得\((p+1)(p-1)\lt S\)\(p\),枚舉可能的\(a_i\)進行遞歸

例四

一個\(n\times m\)的網格,\(k\)種顏色,部分格子已經塗了某種顏色,如今須要將其餘格子也塗上顏色,使得從\((1,1)\)\((n,m)\)的每條路徑(每次向下或向右走一格)都不會出現重複顏色。求方案數,對\(10^9+7\)取模。

\(n,m\le 1000,k\le 10\)

解:

顏色數應大於等於步數,\(n+m-1\le k\Rightarrow n+m\le k+1\le 11\),不然puts("0")。
而後搜每一個位置的顏色,能夠狀壓到每一個位置的已通過的顏色。
可行性剪枝:未通過的顏色數小於剩餘步數,剪掉。
對稱性剪枝:若是塗上一個未出現過的顏色,塗哪個都是等價的,那麼只需搜其中一個。

例五

\(52\)張牌排成一排\(\{0,1\cdots 51\}\),而後能夠把他們分紅兩半,而後交叉着洗牌(一個肯定的置換)。
洗完牌後,可能會出現一個錯誤,將一對相鄰的牌調換了位置,可是每次洗牌最多隻會犯一個錯誤。
如今給出牌最終的順序,求最少洗了多少次(保證不超過\(10\)),最少犯幾回錯誤。

解:

一個性質:肯定了洗牌次數後,若真實的最終排列與不犯錯誤的最終排列,有\(k\)個位置不一樣,那麼犯錯次數\(\lceil\frac{k}{2}\rceil\)

以此來當作剪枝,枚舉洗牌次數和犯錯次數,跑\(idA^*\)

例六

你記錄了\([0,59]\)這個時間段內到站的全部公交車(數量\(\le 300\)) 的時間,每輛車屬於一條線路。

  • 同一路線路的車的到站時間間隔相同。
  • 每條線路在\([0,59]\)至少到達兩輛車。
  • 最多有\(17\)條線路

求最少可能有多少條線路

解:

一條線路,能夠經過第一輛、第二輛車來肯定。
咱們能夠按照到站順序來肯定每輛車的歸屬狀況。
若是它是線路第一輛車,那麼先把他作一個標記。(同時,它的時間應在\([0,29]\))
若是是第二輛,枚舉標記過的車輛來肯定一條線路,若是可行,刪除該線
路的全部車。
這樣,隨着搜索的深度增長,可選車輛快速減小,搜索規模大量下降。

同時能夠加入若干合法性或者最優性剪枝。好比:做爲第一輛的車多於未肯定的車。

例七

給定\(n\)個字符集爲\(\{a,b,c,\cdots,m\}\)的字符串\(S_i\),求一種\(\{a,b,c,\cdots,m\}\)\(\{0,1,2,3,4,5,6,7,8,9,+,\times,=\}\)的映射\(f\),使得全部\(f(S_i)\)均是表達式合法、且成立的等式。

\(n\le 1000,5\le |S_i|\le 11\)

解:

先對約束條件較高的\(+,\times ,=\)進行搜索。 而後,對於每個等式,咱們獲得了每一個數的位數,計算等號兩側的值域, 無交集就能夠剪枝。隨着對應關係的肯定,值域也會愈來愈小。 能夠選擇從低位開始搜,肯定了低位的值,不相等能夠剪枝

相關文章
相關標籤/搜索