NOIp膜你題 Day2node
duliu 出題人:ZAY ios
題解c++
這就是一道組合數問題鴨!!! 但是泥爲何沒有推出式子!!數組
首先咱們知道的是 m 盆花都要擺上,而後他們的順序不定(主人公忘記了)ui
因此初步獲得一個排列數 P( m,m ) , 即 Pmm this
那麼咱們就還剩下 n-m 個空位置,這些空位置都是不能夠放花的,因而咱們逆向思惟一下: spa
n-m 個位置不放花,也就是能夠在這些位置周圍插空放花,把這些位置隔開,那麼就能夠把m盆花放到 n-m+1 個空裏,因爲這是對於空位置來講的,沒有順序可言,每一個都是一毛同樣的,因此獲得一個組合數 C(n-m+1 , m) , 即 Cn-m+1m 3d
因此 ans = P( m,m ) * C( n-m+1 , m ) 指針
= m! * (n-m+1)! / [ m! * (n-m+1 - m)! ]code
化簡一下就是下面
=(n-m+1)*(n-m+1-1)*......*(n-2m+2)
注意
ans 要開 long long , 試過毒了,數據不開 long long 會炸
代碼
#include<bits/stdc++.h> using namespace std; int type,n,m,p; long long ans=1; int main() { freopen("ilove.in","r",stdin); freopen("ilove.out","w",stdout); scanf("%d%d%d%d",&type,&n,&m,&p); if(n==1&&m==1) { printf("1\n"); return 0; } int a=n-m+1,b=n-m-m+2; for(int i=a;i>=b;i--) ans=(ans%p*i%p)%p; printf("%ld\n",ans); return 0; }
看看出題人的題解
通常這種數數題,也就是求方案數,有兩種解決方案:DP&組合數學
1.DP
2.組合數學
Ps:插空法就是我上面題解裏面提到的
題解
注意到根據題目規定的走法,在進入一個節點之後,必須遍歷完它的整個子樹, 不然一旦離開這個節點,再也沒法進入這棵子樹,從而致使該節點的某個孩子沒能放 上石子,致使這個節點不能放上石子。
同時又有每一個節點放上石子之後,它的子樹的 石子能夠所有取回。設在節點 u 放石子須要有 ansu 個石子,則放完 u 之後能夠取回 ansu-wu 個石子。
由於你準備了ansu個石子,u點須要wu個石子放上,那麼放完以後,他的子樹上的石子就能夠取回來了,也就是ansu-wu個,這些也就是節點u的孩子所需石子數和。
因而考慮影響問題答案的顯然是從 u 進入每一個孩子的順序,因爲最多有兩個孩 子,直接比較一下就能夠知道先進入哪一個孩子更優秀了。時間複雜度 O(n)
代碼
#include <cstdio> #include <vector> #include <algorithm> const int maxn = 100010; int n; int MU[maxn], ans[maxn]; //MU也就是W std::vector<int>son[maxn]; void dfs(const int u); bool cmp(const int &_a, const int &_b); int main() { freopen("yin.in", "r", stdin); freopen("yin.out", "w", stdout); scanf("%d", &n); for (int i = 2, x; i <= n; ++i) { scanf("%d", &x); son[x].push_back(i); } for (int i = 1; i <= n; ++i) { scanf("%d", MU + i); //數組名+一個東西 這是指針的寫法 //至關於scanf("%d",&MU[i]); } dfs(1); for (int i = 1; i < n; ++i) { printf("%d ", ans[i]); } printf("%d\n", ans[n]); return 0; } void dfs(const int u) { for (auto v : son[u]) { //遍歷u的全部子節點 dfs(v); } std::sort(son[u].begin(), son[u].end(), cmp); //按照差值不升序排序 int _ret = 0; //此時U節點的全部子節點已經算出來了,準備計算U節點 for (auto v : son[u]) { //葉節點沒有兒子就不會for循環 if (_ret >= ans[v]) //上一個子節點收回的石子足夠擺當前節點,就擺上 { _ret -= ans[v]; //ret擺完以後更新 } else { ans[u] += ans[v] - _ret; //收回的石子不夠放了,往上補 _ret = ans[v] - MU[v]; //遍歷完U的兒子V節點對應的子樹後,收回除了 U的兒子v之外全部節點上的石頭 } } ans[u] += std::max(0, MU[u] - _ret); //深搜,會先到達全部葉節點,而且全部葉節點的ans=w[i] } inline bool cmp(const int &_a, const int &_b) { return (ans[_a] - MU[_a]) > (ans[_b] - MU[_b]); }
題解
1.能夠經過前4個任務點的代碼QWQ
由於咱們看到每封信最多隻會有30個字符對吧,那麼每次詢問給出一個詢問區間的時候,咱們就能夠比對每一位的字符
好比咱們比對到了第 i 位,詢問區間從頭至尾枚舉看一看,對於這一位,有幾個0,幾個1,幾個?
若是既有0又有1,那麼顯然是無解的
若是隻有0和?,那麼這一位只能夠爲0,也就是?只能被填成0
若是隻有1和?,同上
若是全是?,那麼既能夠填0,又能夠填1,也就是有2種方案
咱們把全部位都枚舉了一遍,那麼可能會出現結果:無解,有惟一解,有解但不惟一;對於最後一種狀況顯然是?的數目影響的,乘法分佈原理 2cnt , cnt表示?的數目
代碼
45‘代碼 後邊超時辣
#include<bits/stdc++.h> #include<iostream> #include<cstdio> #include<cstring> #include<string> #include<algorithm> #include<cmath> using namespace std; string s[100009],s1; int n,m,q; int qus,num,l,r; long long ans=0; int main() { freopen("lin.in","r",stdin); freopen("lin.out","w",stdout); scanf("%d%d%d",&n,&m,&q); if(q==0) return 0; for(int i=1;i<=m;i++) cin>>s[i]; for(int i=1;i<=q;i++) { ans=0; scanf("%d",&qus); if(qus==0) { scanf("%d%d",&l,&r); int flag=520,cnt=0; for(int j=0;j<n;j++) { if(flag==0) break; bool glf=0; char mp; mp=s[l][j]; if(mp!='?') glf=0; else glf=1; for(int k=l+1;k<=r;k++) { if(s[k][j]!='?') { if(mp=='?') { glf=1; mp=s[k][j]; continue; } if(mp!='?'&&mp!=s[k][j]) { flag=0; continue; } } else if(s[k][j]=='?') { if(glf==0) glf=1; } } if(mp!='?') glf=0; cnt+=glf; } if(flag==0) {printf("0\n");} else { ans=pow(2,cnt); printf("%ld\n",ans); } } if(qus==1) { scanf("%d",&num); cin>>s1; s[num]=s1; } } return 0; }
我百分之200的錯誤都是由於腦殘,下面這個是整理了一下(貌似並不能夠多得分)
#include<bits/stdc++.h> using namespace std; int n,m,q; string s[100010],s1; int opt,l,r,pos; inline int read() { int ans=0; char last=' ',ch=getchar(); while(ch<'0'||ch>'9') last=ch,ch=getchar(); while(ch>='0'&&ch<='9') ans=ans*10+ch-'0',ch=getchar(); if(last=='-') ans=-ans; return ans; } int main() { freopen("lin.in","r",stdin); freopen("lin.out","w",stdout); n=read(); m=read(); q=read(); for(int i=1;i<=m;i++) cin>>s[i]; for(int i=1;i<=q;i++) { opt=read(); if(opt==1) { pos=read(); cin>>s1; s[pos]=s1; } else { l=read();r=read(); long long ans; int flag=1,cnt=0; for(int j=0;j<n;j++) { if(flag==0) break; int flag0=0,flag1=0,flag2=0; for(int k=l;k<=r;k++) { if(s[k][j]=='0') flag0++; if(s[k][j]=='1') flag1++; if(s[k][j]=='?') flag2++; } if(flag0!=0&&flag1!=0) { flag=0; continue; } else if(flag2==(r-l+1)) cnt++; } if(flag==0) ans=0; else ans=pow(2,cnt); printf("%ld\n",ans); } } return 0; }
固然也能夠用按位與維護
2.考慮正解:線段樹
代碼
忍不了這個指針了
#include <cstdio> template <typename T> inline void qr(T &x) { char ch = getchar(), lst = ' '; while ((ch > '9') || (ch < '0')) lst = ch, ch=getchar(); while ((ch >= '0') && (ch <= '9')) x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar(); if (lst == '-') x = -x; } //快讀 const int maxn = 100010; int n, m, q; char s[maxn][35]; //每封信的內容 #ifdef ONLINE_JUDGE int Ans; #endif struct Tree { Tree *ls, *rs; int l, r, x, y; bool leg; //是否合法 Tree() { ls = rs = NULL; l = r = x = y = 0; leg = true; //先假定合法 } void pushup() { //構造a1,a2,b1,b2 if (!(this->ls->leg && this->rs->leg)) { this->leg = false; } else { if ((this->ls->x & this->rs->x) & (this->ls->y ^ this->rs->y)) { this->leg = false; } else { this->leg = true; this->x = this->ls->x | this->rs->x; this->y = this->ls->y | this->rs->y; } } } }; Tree *rot; //線段樹的根 void ReadStr(char *p); //指針 快讀 void Update(const int x); void Query(const int l, const int r); void update(Tree *const u, const int p); //修改 Tree query(Tree *u, const int l, const int r); //查詢 void build(Tree *const u, const int l, const int r); //建樹 int main() { freopen("lin.in", "r", stdin); freopen("lin.out", "w", stdout); qr(n); qr(m); qr(q); for (int i = 1; i <= m; ++i) { ReadStr(s[i] + 1); } build(rot = new Tree, 1, m); //建樹 int opt, l, r; while (q--) { opt = 0; qr(opt); if (opt == 0) { //查詢 l = r = 0; qr(l); qr(r); Query(l, r); } else { l = 0; qr(l); ReadStr(s[0] + 1); Update(l); //修改第一個 } } #ifdef ONLINE_JUDGE //鬼知道幹啥的 printf("%d\n", Ans); #endif return 0; } void ReadStr(char *p) { do *p = getchar(); while ((*p != '0') && (*p != '1') && (*p != '?')); do *(++p) = getchar(); while ((*p == '0') || (*p == '1') || (*p == '?')); *p = 0; } void build(Tree *const u, const int l, const int r) { if ((u->l = l) == (u->r = r)) { //當前區間在要建的區間內 for (int i = 1; i <= n; ++i) { if (s[l][i] != '?') { u->x |= 1 << i; if (s[l][i] == '1') { u->y |= 1 << i; } } } } else { //不在 int mid = (l + r) >> 1; build(u->ls = new Tree, l, mid); build(u->rs = new Tree, mid + 1, r); u->pushup(); } } Tree query(Tree *u, const int l, const int r) { if ((u->l > r) || (u->r < l)) return Tree(); //當前區間徹底不在查詢區間 if ((u->l >= l) && (u->r <= r)) return *u; //徹底在 Tree _ret; auto ll = query(u->ls, l, r), rr = query(u->rs, l, r); //不然遞歸來求 _ret.ls = ≪ _ret.rs = &rr; _ret.pushup(); return _ret; } void Query(const int l, const int r) { auto _ret = query(rot, l, r); if (!_ret.leg) { //不合法輸出‘0’ #ifndef ONLINE_JUDGE puts("0"); #endif } else { int ans = 1; for (int i = 1; i <= n; ++i) if (!(_ret.x & (1 << i))) { ans <<= 1; } #ifdef ONLINE_JUDGE Ans ^= ans; #else printf("%d\n", ans); #endif } } void update(Tree *u, const int p) { //修改第p個 if (u->ls) { //尋找p if (u->ls->r >= p) { //左子樹右端點比p大 update(u->ls, p); } else { update(u->rs, p); } u->pushup(); //把子節點的信息合併到父節點 } else { *u = Tree(); u->l = u->r = p; for (int i = 1; i <= n; ++i) { if (s[0][i] != '?') { u->x |= 1 << i; if (s[0][i] == '1') { u->y |= 1 << i; } } } } } void Update(const int x) { update(rot, x); //修改 }
拒絕理解Zay的代碼
放一隻water lift的代碼(線段樹板子)
#include <bits/stdc++.h> using namespace std; template <class T> inline void read(T &num) { bool flag = 0; num = 0; char c = getchar(); while ((c < '0' || c > '9') && c != '-') c = getchar(); if (c == '-') { flag = 1; c = getchar(); } num = c - '0'; c = getchar(); while (c >= '0' && c <= '9') num = (num << 3) + (num << 1) + c - '0', c = getchar(); if (flag) num *= -1; } template <class T> inline void output(T num) { if (num < 0) { putchar('-'); num = -num; } if (num >= 10) output(num / 10); putchar(num % 10 + '0'); } template <class T> inline void outln(T num) { output(num); putchar('\n'); } template <class T> inline void outps(T num) { output(num); putchar(' '); } const int N = 31, M = 100010; int n, m, q; struct segment { char val[M]; bool all1[M * 4]; bool all0[M * 4]; void init(int node, int nl, int nr)//這是個正常的線段樹的板子(雖然位置好像不大正常) { if (nl < nr) { int mid = (nl + nr) >> 1; init(node * 2, nl, mid); init(node * 2 + 1, mid + 1, nr); all1[node] = all1[node * 2] & all1[node * 2 + 1]; all0[node] = all0[node * 2] & all0[node * 2 + 1]; } else { if (val[nl] == '?') all1[node] = all0[node] = 1; else { all1[node] = val[nl] == '1'; all0[node] = val[nl] == '0'; } } } void modify(int node, int nl, int nr, int x, char va) { if (val[x] == va) return; if (nl < nr) { int mid = (nl + nr) >> 1; if (x <= mid) { modify(node * 2, nl, mid, x, va); } else { modify(node * 2 + 1, mid + 1, nr, x, va); } all1[node] = all1[node * 2] & all1[node * 2 + 1]; all0[node] = all0[node * 2] & all0[node * 2 + 1]; } else { if (va == '?') all1[node] = all0[node] = 1; else { all1[node] = va == '1'; all0[node] = va == '0'; } val[x] = va; } } pair<bool, bool> query(int node, int nl, int nr, int l, int r) { if (l <= nl && r >= nr) { return make_pair(all1[node], all0[node]); } int mid = (nl + nr) >> 1; bool a1 = true, a0 = true; if (l <= mid) { auto lo = query(node * 2, nl, mid, l, r); a1 &= lo.first; a0 &= lo.second; } if (r >= mid + 1) { auto lo = query(node * 2 + 1, mid + 1, nr, l, r); a1 &= lo.first; a0 &= lo.second; } return make_pair(a1, a0); } void dfs(int node, int nl, int nr) { if (nl < nr) { int mid = (nl + nr) >> 1; dfs(node * 2, nl, mid); dfs(node * 2 + 1, mid + 1, nr); } outps(nl); outps(nr); outps(all1[node]); outln(all0[node]); } } segs[N]; int main() { freopen("lin.in", "r", stdin); freopen("lin.out", "w", stdout); read(n); read(m); read(q); char ch; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { do { ch = getchar(); } while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\0'); segs[j].val[i] = ch; } } for (int i = 1; i <= n; i++) { segs[i].init(1, 1, m); } while (q--) { bool opt; read(opt); if (opt == 0) { int l, r; read(l); read(r); int ans = 1; for (int i = 1; i <= n; i++) { auto lo = segs[i].query(1, 1, m, l, r); ans *= (lo.first + lo.second); } outln(ans); } else { int pos; read(pos); for (int i = 1; i <= n; i++) { do { ch = getchar(); } while (ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t' || ch == '\0'); segs[i].modify(1, 1, m, pos, ch); } } } }
彩蛋:
考試暴露了不少小問題
1.文件的讀寫
安利一個方法:如何比較本身的代碼ans和答案out是否一致(不只是用在NOIP上,還有NOI,IOI)
(1)鍵盤按住 shift 鍵,同時鼠標右鍵單擊
(2)會崩出一個會話框,點擊「在此處打開命令窗口」,win10不大同樣,好像是f**忘了
(3)就會出現這個東西
不輸入cmd 也能夠,由於它自己就是cmd
(4)輸入 fc 文件1 文件2
若是不一樣的話
若是相同的話
這樣你就能夠看看本身的答案對不對,錯在哪裏QWQ
2.數據分治
也就是對於不一樣的子任務你能夠用不一樣的方法解決掉,從而騙取更多分數
3.編譯&加文件的順序
大型考試的時候呢,若是你代碼一旦進行了修改,那麼必定要從新試一遍樣例
freopen就不要註釋掉啦
4.題目難度和順序安排不必定有瓜