啊,來了五天(今天應該是第六天了)camp了,終於可以愉快的補題了,由於前面欠下的題目好像有點多,所以只能從後往前將題目以及題解補上了(希望能在有生之年把能補的題目補完吧,QAQ)。
第五天是dls場,dls對待菜雞還是非常友好的,div2的題目簡直是快樂無比,div1的就不快樂了,但還是得把能補的題目補一補。
A.Cactus Draw
div1版本:給你一棵仙人掌,要將所有節點放到二維平面中,同時使得所有邊都沒有交點,輸出構造方案。(暫時不會,待補)
div2版本:給一棵樹,其他同div1一樣。
簡易題解:如果是一棵樹,這個題目就是一個非常快樂的題目了,只需要dfs一遍,計算一下每個節點的深度,然後按深度從上往下放,深度相同的從左往右放就可以了。
參考代碼:
#include <bits/stdc++.h>
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;
#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<" "<<#z<<(z)<<"]\n"
typedef double db;
typedef long long ll;
typedef pair<int,int>pii;
typedef pair<ll,ll>pll;
const db eps = 1e-9;
const db pi = acos(-1.0);
const int MX = 1000 + 5;
//head
int n,m;
int mx = 0;
vector<int>G[MX],dep[MX];
void dfs(int u,int fa,int d){
dep[d].pb(u);
mx = max(mx,d);
for(auto v : G[u]){
if(v == fa) continue;
dfs(v,u,d+1);
}
}
pii ans[MX];
int main(){
#ifdef LOCAL
FIN;
#endif
scanf("%d%d",&n,&m);
for(int i = 1;i <= m;i++){
int u,v;
scanf("%d%d",&u,&v);
G[u].pb(v);G[v].pb(u);
}
dfs(1,0,1);
for(int i = 1;i <= mx;i++){
for(int j = 0;j < dep[i].size();j++)
ans[dep[i][j]] = MP(i,j+1);
}
for(int i = 1;i <= n;i++)
printf("%d %d\n",ans[i].fi,ans[i].se);
return 0;
}
B.Diameter(待補)
div1版本:所有n個節點有標號的無根樹,直徑爲0,1,…,n−1的樹有多少個。(n<=500)
div2版本:題意同div2,n<=11
C.Division
div1版本:你有一個數列a[1], a[2],...,a[n-1],a[n]。進行q次詢問,每次詢問對區間 [l,r] 內的數你可以進行這樣的一次操作,每次選擇數列中其中一個數然後將其除2下取整,每次詢問只能對這些數進行不超過k次操作,如何操作才能使得這些數的和儘可能的小。
簡易題解:我們可以把問題轉化一下,因爲每次操作都是將一個數除二向下取整,我們可以看做減去了某一個數,這樣對於每個數a[i]來說,這個數最多進行log(a[i])次操作之後,這個數就會變成0了,我們可以把這log(a[i])次減掉的數都算出來。而每次都是要對區間內的數進行k次操作,所以我們就可以看成是將這個區間內每個數操作後會減去的數的前k大數減掉,就是最終的結果了。
這樣我們就可以用一個主席樹來維護這n×log(a[i])個數,然後區間查詢前k大和就可以解決這個問題了。
時間複雜度爲O(n*log(a[i])*logn+q*logn),空間複雜度爲O(n*log(a[i])*logn)。
但是,,,dls的題目怎麼會這麼簡單,如果按照這個做法,你將會收穫到一個MLE(手算一下可以發現256M的內存根本不夠開下O(n*log(a[i])*logn)的空間。
所以我們要進行一些騷操作對空間進行優化。(聽完dls講課感覺這個做法真的是太神仙了,dlstxdy!)
前面說了,每次除2我們都可以看做是一次減法,那麼我們就可以把查詢離線下來。
然後枚舉k=30到0,每次枚舉看有哪些數在一次操作之後所減小的值在[2^(k-1),2^(k)]這個區間內,然後對這些值再重新建立主席樹,再將每次詢問的答案進行更新,當然這裏還得做一個前綴和優化,不然還是會TLE的。對於每個操作次數k大於你所查詢的數減少的次數的情況,我們可以用一個前綴和來記錄這些所減少的數值,直接減掉即可,否則再用主席樹進行一次查詢,這樣就可以避免每次都進行一次查詢,將複雜度從q×logn×30降到了q×logn。
這樣由於每次對於所枚舉的區間都是重新建立一次主席樹,所以只需要n×logn的空間就可以了。
這樣的時間複雜度就是O(n*logn*30 + q * logn),空間複雜度是O(n*logn),就可以通過這個題目了。
(ps:可能是我常數太大了,還得加個fastIO才能過,大常數選手流下了鶸雞的淚水
參考代碼:
#include <bits/stdc++.h>
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int>pii;
#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<" "<<#z<<" "<<z<<"]\n"
const double pi = acos(-1.0);
const double eps = 1e-8;
const int MX = 1e5 + 7;
//head
struct FastIO {
static const int S = 1e7;
int wpos;
char wbuf[S];
FastIO() : wpos(0) {}
inline int xchar() {
static char buf[S];
static int len = 0, pos = 0;
if (pos == len)
pos = 0, len = fread(buf, 1, S, stdin);
if (pos == len) exit(0);
return buf[pos++];
}
inline int xuint() {
int c = xchar(), x = 0;
while (c <= 32) c = xchar();
for (; '0' <= c && c <= '9'; c = xchar()) x = x * 10 + c - '0';
return x;
}
inline int xint() {
int s = 1, c = xchar(), x = 0;
while (c <= 32) c = xchar();
if (c == '-') s = -1, c = xchar();
for (; '0' <= c && c <= '9'; c = xchar()) x = x * 10 + c - '0';
return x * s;
}
inline void xstring(char *s) {
int c = xchar();
while (c <= 32) c = xchar();
for (; c > 32; c = xchar()) * s++ = c;
*s = 0;
}
inline void wchar(int x) {
if (wpos == S) fwrite(wbuf, 1, S, stdout), wpos = 0;
wbuf[wpos++] = x;
}
inline void wint(ll x) {
if (x < 0) wchar('-'), x = -x;
char s[24];
int n = 0;
while (x || !n) s[n++] = '0' + x % 10, x /= 10;
while (n--) wchar(s[n]);
wchar('\n');
}
inline void wstring(const char *s) {
while (*s) wchar(*s++);
}
~FastIO() {
if (wpos) fwrite(wbuf, 1, wpos, stdout), wpos = 0;
}
} io;
int n, m;
int a[MX], num[MX], tot;
int root[MX];
ll pre_sum[MX];
struct tree {
int ls, rs, s;
ll sum;
} T[MX << 5];
struct que {
int l, r, k;
ll ans;
} q[MX * 5];
inline void push_up(int rt) {
int ls = T[rt].ls, rs = T[rt].rs;
T[rt].s = T[ls].s + T[rs].s;
T[rt].sum = T[ls].sum + T[rs].sum;
}
void update(int l, int r, int &rt, int pre, int x) {
rt = ++tot; T[rt] = T[pre];
if (l == r) {
++T[rt].s;
T[rt].sum += x - (x >> 1);
return;
}
int mid = (l + r) >> 1;
if (x <= mid) update(l, mid, T[rt].ls, T[pre].ls, x);
else update(mid + 1, r, T[rt].rs, T[pre].rs, x);
push_up(rt);
}
ll query(int l, int r, int rt, int pre, int k) {
if (l == r) return ll(k * (l - (l >> 1)));
int ls = T[rt].ls, rs = T[rt].rs;
int LS = T[pre].ls, RS = T[pre].rs;
int mid = (l + r) >> 1;
if (k > T[rs].s - T[RS].s)
return query(l, mid, ls, LS, k - (T[rs].s - T[RS].s)) + (T[rs].sum - T[RS].sum);
else
return query(mid + 1, r, rs, RS, k);
}
int main() {
#ifdef ONLINE_JUDGE
#else
FIN;
#endif
n = io.xint(); m = io.xint();
for (int i = 1; i <= n; ++i) {
a[i] = io.xint();
pre_sum[i] = pre_sum[i - 1] + a[i];
}
for (int i = 1; i <= m; ++i) {
q[i].l = io.xint();
q[i].r = io.xint();
q[i].k = io.xint();
q[i].ans = pre_sum[q[i].r] - pre_sum[--q[i].l];
}
for (int i = 29; i >= 0; --i) {
int L = 1 << i, R = 2 << i;
tot = 0;
for (int j = 1; j <= n; ++j) {
if ((a[j] >> i) & 1) {
update(L, R, root[j], root[j - 1], a[j]);
pre_sum[j] = pre_sum[j - 1] + a[j] - (a[j] >> 1);
num[j] = num[j - 1] + 1;
} else {
root[j] = root[j - 1];
pre_sum[j] = pre_sum[j - 1];
num[j] = num[j - 1];
}
}
for (int j = 1; j <= m; ++j) {
if (q[j].k && q[j].k >= num[q[j].r] - num[q[j].l]) {
q[j].k -= num[q[j].r] - num[q[j].l];
q[j].ans -= pre_sum[q[j].r] - pre_sum[q[j].l];
} else {
q[j].ans -= query(L, R, root[q[j].r], root[q[j].l], q[j].k);
q[j].k = 0;
}
}
for (int j = 1; j <= n; ++j) if ((a[j] >> i) & 1) a[j] >>= 1;
}
for (int i = 1; i <= m; i++)
io.wint(q[i].ans);
return 0;
}
div2版本:僅僅只有一次詢問,其他同div1。
簡易題解:這又是一個div2比div1快樂一百倍的題目,由於每個數最大進行logn次操作就會變成0了,所以當操作次數k大於n×logn次,那麼整個序列就會變成全零,對於k小於等於n×logn次的情況,暴力處理就可以了,用一個優先隊列維護最大的數即可,每次取出值最大的數處理就行。
參考代碼:
#include <bits/stdc++.h>
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;
#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<" "<<#z<<(z)<<"]\n"
typedef double db;
typedef long long ll;
typedef pair<int,int>pii;
typedef pair<ll,ll>pll;
const db eps = 1e-9;
const db pi = acos(-1.0);
const int MX = 1e5 + 5;
//head
int n,k;
int a[MX];
priority_queue<int>q;
int main(){
#ifdef LOCAL
FIN;
#endif
scanf("%d%d",&n,&k);
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
q.push(a[i]);
}
if(k >= 31 * n) printf("0\n");
else{
while(k--){
int now = q.top();q.pop();
q.push(now/2);
}
ll ans = 0;
while(!q.empty()){
ans += q.top();
q.pop();
}
printf("%lld\n",ans);
}
return 0;
}
D.Doppelblock
這是一個div1和div2相同的題目,題目是中文,就不寫了-,-。
簡易題解:n最大隻有7,很明顯就是一個搜索題目。先按照題目的要求將所有限制條件加上,然後就開始無盡的剪枝之旅了。正式比賽的時候,剪枝剪到意識模糊都沒過,後面再看代碼的時候才發現當時寫了很多沒用的東西,寫這種鬼畜的東西還是得頭腦清醒的時候才能寫。
這個題只需要加幾個關鍵的剪枝就可以過了。
對於每一行和每一列,我們都可以維護兩個值sum、sum1。sum表示這一行(列)還沒有填“X”的時候已經填了的數的和,sum1表示這一行(列)已經填了一個"X"之後,從第一個“X”開始到第二個“X”的數的和。由於每一行填下的數的總和是固定的,如果當前這一位要填的數加上sum要大於每行總和減去這位數所在的行(列)的限制和的話,就不用繼續枚舉了。同理,對於填了一個“X”的情況,如果當前這一位要填的數加上sum1要大於這位數所在的行(列)的限制和的話,也是可以不用繼續往下枚舉的。
還有就是如果一行(列)已經填了兩個“X”的話,直接判斷一下這行的sum1是否等於限制和就可以了。
加入以上剪枝的話,就可以過了。
當然還有更優的寫法,就是先枚舉所有“X”的填充情況,再進行填數,這樣可以快很多。(但我太懶了,就沒寫)。
參考代碼:
#include <bits/stdc++.h>
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int>pii;
#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<" "<<#z<<" "<<z<<"]\n"
const double pi = acos(-1.0);
const double eps = 1e-8;
const int MX = 8;
//head
int n, _, sum;
int r[MX], c[MX];
int rsum[MX], csum[MX];
int rsum1[MX], csum1[MX];
bool rvis[MX][MX], cvis[MX][MX];
int rtag[MX], ctag[MX];
int ans[MX][MX];
void init() {
clr(ans);
clr(rsum); clr(csum);
clr(rsum1); clr(csum1);
clr(rvis); clr(cvis);
clr(rtag); clr(ctag);
}
bool ok;
void show(int x, int y) {
debug2(x, y);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++)
printf("%d%c", ans[i][j], " \n"[j == n]);
}
printf("\n");
}
void dfs(int x, int y) {
if (ok) return;
if (rtag[x] == 2 && rsum1[x] != r[x]) return;
if (ctag[y] == 2 && csum1[y] != c[y]) return;
if (x == n && y == n) {
if (ctag[y] != 2 || csum1[y] != c[y]) return;
if (rtag[x] != 2 || rsum1[x] != r[x]) return;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (ans[i][j] == -1) printf("X");
else printf("%d", ans[i][j]);
}
printf("\n");
}
ok = 1;
return;
}
if (x == n)
if (ctag[y] != 2 || csum1[y] != c[y]) return;
if (y == n) {
if (rtag[x] != 2 || rsum1[x] != r[x]) return;
for (int i = 0; i < n - 1; i++) {
if (ok) return;
if (i == 0) {
if (ctag[1] == 2) continue;
ans[x + 1][1] = -1;
rtag[x + 1]++; ctag[1]++;
dfs(x + 1, 1);
ans[x + 1][1] = 0;
rtag[x + 1]--; ctag[1]--;
} else {
if (ctag[1] == 0 && i + csum[1] > sum - c[1]) return;
if (ctag[1] == 1 && i + csum1[1] > c[1]) return;
if (rvis[x + 1][i] || cvis[1][i]) continue;
ans[x + 1][1] = i;
rsum[x + 1] += i;
if (ctag[1] == 0) csum[1] += i;
else if (ctag[1] == 1) csum1[1] += i;
rvis[x + 1][i] = 1; cvis[1][i] = 1;
dfs(x + 1, 1);
ans[x + 1][1] = 0;
rsum[x + 1] -= i;
if (ctag[1] == 0) csum[1] -= i;
else if (ctag[1] == 1) csum1[1] -= i;
rvis[x + 1][i] = 0; cvis[1][i] = 0;
}
}
return;
}
for (int i = 0; i < n - 1; i++) {
if (ok) return;
if (i == 0) {
if (rtag[x] == 2 || ctag[y + 1] == 2) continue;
ans[x][y + 1] = -1;
rtag[x]++; ctag[y + 1]++;
dfs(x, y + 1);
ans[x][y + 1] = 0;
rtag[x]--; ctag[y + 1]--;
} else {
if (rvis[x][i] || cvis[y + 1][i]) continue;
if ((y + 1 == n && rtag[x] == 1) || (y + 1 == n - 1 && rtag[x] == 0)) return;
if (rtag[x] == 0 && i + rsum[x] > sum - r[x]) return;
if (rtag[x] == 1 && i + rsum1[x] > r[x]) return;
if (ctag[y + 1] == 0 && i + csum[y + 1] > sum - c[y + 1]) return;
if (ctag[y + 1] == 1 && i + csum1[y + 1] > c[y + 1]) return;
ans[x][y + 1] = i;
rvis[x][i] = cvis[y + 1][i] = 1;
if (rtag[x] == 0) rsum[x] += i;
else if (rtag[x] == 1) rsum1[x] += i;
if (ctag[y + 1] == 0) csum[y + 1] += i;
else if (ctag[y + 1] == 1) csum1[y + 1] += i;
dfs(x, y + 1);
ans[x][y + 1] = 0;
rvis[x][i] = cvis[y + 1][i] = 0;
if (rtag[x] == 0) rsum[x] -= i;
else if (rtag[x] == 1) rsum1[x] -= i;
if (ctag[y + 1] == 0) csum[y + 1] -= i;
else if (ctag[y + 1] == 1) csum1[y + 1] -= i;
}
}
}
int main() {
// FIN;
int t = 0;
for (scanf("%d", &_); _; _--) {
if (t) puts("");
t++;
init();
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &r[i]);
for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
sum = (1 + (n - 2)) * (n - 2) / 2;
ok = 0;
for (int i = 0; i < n - 1; i++) {
if (ok) break;
if (i == 0) {
ans[1][1] = -1;
rtag[1]++; ctag[1]++;
dfs(1, 1);
ans[1][1] = 0;
rtag[1]--; ctag[1]--;
} else {
ans[1][1] = i;
rsum[1] += i; csum[1] += i;
rvis[1][i] = 1; cvis[1][i] = 1;
dfs(1, 1);
ans[1][1] = 0;
rsum[1] -= i; csum[1] -= i;
rvis[1][i] = 0; cvis[1][i] = 0;
}
}
}
return 0;
}
E.Fast Kronecker Transform
一個神奇的卷積題目。
這個題我們如果考慮枚舉值去直接做NTT的話,在NTT中就是將所有值等於 i 的位上的值附值爲那一位,否則就爲0。
這樣對於每一個值 i 都做一遍NTT是可以得到答案的。
但是如果某一個值所有的數的個數很少的話,直接做NTT是很慢的(因爲考慮到每次都要初始化NTT中的數組,而且NTT中需要進行多次的取模運算,所以就要慢的多)。
對於值的個數較小的情況,我們就考慮直接用暴力做,這樣就可以節省下較多的時間,對於值較多的情況,再考慮用NTT,就可以降低複雜度了。
假設我們設置的閾值爲T,那麼複雜度大致就是O(num*T+n/T*(n/T)*log(n/T)),接下來就是進行快樂地調參了~
參考代碼:
#include <bits/stdc++.h>
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int>pii;
#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<" "<<#z<<" "<<z<<"]\n"
const double pi = acos(-1.0);
const double eps = 1e-8;
const int MX = 1e5 + 5;
//head
const int P = 998244353;
const int G = 3;
const int NUM = 20;
ll wn[NUM];
ll va[MX << 2], vb[MX << 2];
ll quick_mod(ll a, ll x, ll mod) {
ll ans = 1;
a %= mod;
while (x) {
if (x & 1)ans = ans * a % mod;
x >>= 1;
a = a * a % mod;
}
return ans;
}
void GetWn() {
for (int i = 0; i < NUM; i++) {
int t = 1 << i;
wn[i] = quick_mod(G, (P - 1) / t, P);
}
}
void Rader(ll F[], int len) {
int j = len >> 1;
for (int i = 1; i < len - 1; i++) {
if (i < j) swap(F[i], F[j]);
int k = len >> 1;
while (j >= k)j -= k, k >>= 1;
if (j < k) j += k;
}
}
void NTT(ll F[], int len, int t) {
Rader(F, len);
int id = 0;
for (int h = 2; h <= len; h <<= 1) {
id++;
for (int j = 0; j < len; j += h) {
ll E = 1;
for (int k = j; k < j + h / 2; k++) {
ll u = F[k];
ll v = E * F[k + h / 2] % P;
F[k] = (u + v) % P;
F[k + h / 2] = (u - v + P) % P;
E = E * wn[id] % P;
}
}
}
if (t == -1) {
for (int i = 1; i < len / 2; i++)swap(F[i], F[len - i]);
ll inv = quick_mod(len, P - 2, P);
for (int i = 0; i < len; i++)F[i] = F[i] * inv % P;
}
}
void Conv(ll a[], ll b[], int len) {
NTT(a, len, 1);
NTT(b, len, 1);
for (int i = 0; i < len; i++) a[i] = a[i] * b[i] % P;
NTT(a, len, -1);
}
int n, m;
int a[MX], b[MX];
vector<int>h, la[MX], lb[MX];
int get_id(int x) {
return lower_bound(all(h), x) - h.begin() + 1;
}
ll ans[2 * MX];
void upd(ll &x, ll y) {
x += y;
if (x >= P) x -= P;
}
int main() {
#ifdef ONLINE_JUDGE
#else
FIN;
#endif
GetWn();
scanf("%d%d", &n, &m);
for (int i = 0; i <= n; ++i) {
scanf("%d", &a[i]);
h.pb(a[i]);
}
for (int i = 0; i <= m; ++i) {
scanf("%d", &b[i]);
h.pb(b[i]);
}
sort(all(h));
h.erase(unique(all(h)), h.end());
for (int i = 0; i <= n; ++i) {
a[i] = get_id(a[i]);
la[a[i]].pb(i);
}
for (int i = 0; i <= m; ++i) {
b[i] = get_id(b[i]);
lb[b[i]].pb(i);
}
int len = 1;
while (len <= n + m + 1) len <<= 1;
int up = 4000;
for (int i = 1; i <= (int)h.size(); ++i) {
if (min(la[i].size() , lb[i].size()) <= up) {
for (auto x : la[i]) for (auto y : lb[i]) upd(ans[x + y], (ll)x * y % P);
} else {
for (int j = 0; j <= n; ++j) va[j] = (a[j] == i) ? j : 0;
for (int j = n + 1; j < len; ++j) va[j] = 0;
for (int j = 0; j <= m; ++j) vb[j] = (b[j] == i) ? j : 0;
for (int j = m + 1; j < len; ++j) vb[j] = 0;
Conv(va, vb, len);
for (int j = 0; j <= n + m; ++j) upd(ans[j], va[j]);
}
}
for (int i = 0; i <= n + m; ++i)
printf("%lld%c", ans[i], " \n"[i == n + m]);
return 0;
}
F.Kropki
div1版本:你有一個1到n的排列p[1],p[2],p[3],...,p[n],對於所有的i (1≤ i ≤n−1),如果p[i] 和 p[i+1]中,有一個數是另一個的兩倍,那麼會在這兩個數之間畫上一個點,否則不會。
現在你把所有數字都擦掉了,只剩下了這些點,請問有多少種1到n的排列滿足條件。(n<=40)(待補)
div2版本:n<=15,其他同div1。
簡易題解:這又是一個div2比div1快樂不知道多少的題目,(雖然隊友昨天下午wa到無法自拔)。
由於n<=15,所以就可以直接考慮做狀壓dp了,具體咋轉移還沒細想(畢竟是隊友寫的,逃
參考代碼:
#include <bits/stdc++.h>
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
#define LL long long
using namespace std;
#define fuck1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define fuck2(x,y) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<"]\n"
#define fuck3(x,y,z) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<" "<<#z<<(z)<<"]\n"
typedef long long ll;
typedef pair<int,int>pii;
typedef pair<ll,ll>pll;
const int mod = 1e9+7;
const int MX = 1<<16;
int dp[16][MX][16];
int vis[16];
char s[16];
inline void upd(int &x){
if(x > mod) x -= mod;
}
int main()
{
#ifdef LOCAL
FIN;
#endif // LOCAL
int n;
scanf("%d",&n);
scanf("%s",s+1);
for(int i = 1; i < n; i++)
vis[i] = s[i]-'0';
for(int i = 0; i < n; i++)
dp[0][1<<i][i] = 1;
int SS = (1<<n)-1;
for(int i = 1; i < n; i++){
for(int s = 1; s <= SS; s++) {
for(int k = 0; k < n; k++) if(dp[i-1][s][k] > 0){
if(!vis[i]) {
for(int l = 0; l < n; l++) if( ((1<<l)&s) == 0 && l != 2*k+1 && 2*l+1 != k){
dp[i][s|(1<<l)][l] += dp[i-1][s][k];
upd(dp[i][s|(1<<l)][l]);
}
}
else{
if(2*k+1 < n && ((1<<(2*k+1))&s) == 0){
dp[i][s|(1<<(2*k+1))][2*k+1] += dp[i-1][s][k];
upd(dp[i][s|(1<<(2*k+1))][2*k+1]);
}
if((k&1) && ((1<<(k/2))&s) == 0){
dp[i][s|(1<<(k/2))][k/2] += dp[i-1][s][k];
upd(dp[i][s|(1<<(k/2))][k/2]);
}
}
}
}
}
int ans = 0;
for(int i = 0; i < n; i++){
ans += dp[n-1][SS][i];
upd(ans);
}
cout<<ans<<endl;
}
G.Least Common Multiple(待補)
作爲一個神仙題,這個題很好的盡到了它的職責。昨天聽dls講題都聽得一臉懵逼,感覺大概率是咕咕咕了?(逃
H.Nested Tree
div1版本:你有一棵n個點樹T,然後你把它複製了m遍,然後在這m棵樹之間又加了m−1條邊,變成了一棵新的有nm個點的樹T2。求T2中所有點對的距離和.(n,m<=1e5)
簡易題解:好幾天前就想補這題了,由於種(wo)種(xiang)原(mo)因(yu),到今天才把這題補完,人類的本質果然是鴿子~
由於div1版本的n和m都是1e5,所以我們沒辦法直接建樹去考慮每條邊的貢獻,但仍舊是沿着這個思路去做的,考慮每條邊的貢獻。
對於大的樹,我們也可以直接做一遍dfs,算出所有連接複製的樹之間的邊的貢獻。
接下來,我們可以考慮每一棵小樹中的邊對於答案的貢獻,我們知道每條邊對答案的貢獻就是 ,所表示的就是這條邊連接的一端的點(深度較大的點)所在的子樹大小。
那麼對於這題,同樣也可以這麼去求,我們可以把這個看成是若干棵小樹懸掛在當前這棵樹的下方(我們假設所有小子樹的根節點都是1)。
比如樣例中,對於複製後的第一份的小樹來說,就可以把整棵樹看成是如下的樣子:
這樣對於第一個複製的小樹來說,節點1的子樹大小就爲9,節點2的子樹大小爲8,節點3的子樹大小爲4,所以這棵小子樹對最終答案的貢獻爲:;
同理,第二個複製的小樹,節點1的子樹大小就爲9,節點2的子樹大小爲2,節點3的子樹大小爲1,所以這棵小子樹對最終答案的貢獻爲:;
第二個複製的小樹,節點1的子樹大小就爲9,節點2的子樹大小爲8,節點3的子樹大小爲1,所以這棵小子樹對最終答案的貢獻爲:;
再加上每條連接複製樹直接的邊的貢獻。
那現在的問題就轉化成了,該如何維護每棵小子樹內所有節點的子樹大小了。
我們可以看出來,在點u懸掛一棵子樹之後,只會對根節點到點u這條鏈上的點的子樹大小有影響,那麼我們就可以用樹鏈剖分對這條鏈上所有的點的子樹大小進行更新。
但由於每棵子樹中節點個數都很多,如果一個節點一個節點算的話,時間複雜度是過不去的。
現在我們假設在當前所枚舉的子樹中,節點的子樹大小爲,那麼這棵子樹對答案最終的貢獻就爲。(因爲選擇節點1爲根節點的話,就沒有一條邊是以節點1爲深度更深的節點了,所以就不考慮節點1).
考慮拆開一下括號,則可得,由於n*m是一個常數,那麼我們只需要維護和就可以了。
可以藉助線段樹來維護。
對於同樣也可以用線段樹來維護,根據,我們只需要在線段樹更新的時候,把的和按照這個式子更新即可,即(假設子樹大小增加了d)。
這樣就可以通過枚舉每棵子樹算其對答案的貢獻解決這個問題了,時間複雜度爲O(n*logn*logn)。
在寫的時候注意下取模的細節就可以了。
參考代碼:
#include <bits/stdc++.h>
#define fi first
#define se second
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int>pii;
typedef pair<ll, ll>pll;
#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<" "<<#z<<" "<<z<<"]\n"
const double pi = acos(-1.0);
const double eps = 1e-8;
const int MX = 1e5 + 7;
const int mod = 1e9 + 7;
int n, m;
vector<int>g[MX];
vector<pii>G[MX];
int SZ[MX], dep1[MX];
ll ans, all;
void dfs(int u, int fa) {
SZ[u] = 1; dep1[u] = dep1[fa] + 1;
for (auto now : G[u]) {
int v = now.fi;
if (v == fa) continue;
dfs(v, u);
SZ[u] += SZ[v];
ll A = (ll)SZ[v] * n % mod;
ll B = (all - A + mod) % mod;
ans = (ans % mod + A * B % mod) % mod;
}
}
int dep[MX], sz[MX], fa[MX], son[MX], top[MX], id[MX], _id[MX], tot;
void dfs1(int u) {
sz[u] = 1; son[u] = 0;
for (auto v : g[u]) {
if (v == fa[u]) continue;
fa[v] = u; dep[v] = dep[u] + 1;
dfs1(v);
sz[u] += sz[v];
if (sz[v] > sz[son[u]]) son[u] = v;
}
}
void dfs2(int u, int tp) {
id[u] = ++tot; _id[tot] = u; top[u] = tp;
if (son[u]) dfs2(son[u], tp);
for (auto v : g[u]) {
if (v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
void pre_solve() {
tot = 0;
dfs1(1); dfs2(1, 1);
}
ll sum[MX << 2], sq_sum[MX << 2], tag[MX << 2];
void push_up(int rt) {
sum[rt] = (sum[rt << 1] + sum[rt << 1 | 1]) % mod;
sq_sum[rt] = (sq_sum[rt << 1] + sq_sum[rt << 1 | 1]) % mod;
}
void push_down(int l, int r, int rt) {
if (tag[rt]) {
int mid = (l + r) >> 1;
sq_sum[rt << 1] = (sq_sum[rt << 1] % mod + 2ll * tag[rt] % mod * sum[rt << 1] % mod + tag[rt] * tag[rt] % mod * (mid - l + 1) % mod) % mod;
sum[rt << 1] = (sum[rt << 1] % mod + tag[rt] * (mid - l + 1) % mod) % mod;
sq_sum[rt << 1 | 1] = (sq_sum[rt << 1 | 1] + 2ll * tag[rt] % mod * sum[rt << 1 | 1] % mod + tag[rt] * tag[rt] % mod * (r - mid) % mod) % mod;
sum[rt << 1 | 1] = (sum[rt << 1 | 1] % mod + tag[rt] * (r - mid) % mod) % mod;
tag[rt << 1] += tag[rt]; tag[rt << 1] %= mod;
tag[rt << 1 | 1] += tag[rt]; tag[rt << 1 | 1] %= mod;
tag[rt] = 0;
}
}
void build(int l, int r, int rt) {
if (l == r) {
sum[rt] = sz[_id[l]];
sq_sum[rt] = (ll)sz[_id[l]] * sz[_id[l]] % mod;
return;
}
int mid = (l + r) >> 1;
build(lson);
build(rson);
push_up(rt);
}
void update(int L, int R, ll d, int l, int r, int rt) {
if (L <= l && r <= R) {
sq_sum[rt] = (sq_sum[rt] % mod + 2ll * d % mod * sum[rt] % mod + d * d % mod * (r - l + 1) % mod) % mod;
sum[rt] = (sum[rt] % mod + d * (r - l + 1) % mod) % mod;
tag[rt] += d; tag[rt] %= mod;
return;
}
push_down(l, r, rt);
int mid = (l + r) >> 1;
if (L <= mid) update(L, R, d, lson);
if (R > mid) update(L, R, d, rson);
push_up(rt);
}
ll query_sum(int L, int R, int l, int r, int rt) {
if (L <= l && r <= R) return sum[rt];
push_down(l, r, rt);
ll res = 0;
int mid = (l + r) >> 1;
if (L <= mid) res = (res + query_sum(L, R, lson)) % mod;
if (R > mid) res = (res + query_sum(L, R, rson)) % mod;
return res;
}
ll query_sq(int L, int R, int l, int r, int rt) {
if (L <= l && r <= R) return sq_sum[rt];
push_down(l, r, rt);
ll res = 0;
int mid = (l + r) >> 1;
if (L <= mid) res = (res + query_sq(L, R, lson)) % mod;
if (R > mid) res = (res + query_sq(L, R, rson)) % mod;
return res;
}
void upd(int u, int v, ll d) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
update(id[top[u]], id[u], d, 1, n, 1);
u = fa[top[u]];
}
if (dep[u] > dep[v]) swap(u, v);
update(id[u], id[v], d, 1, n, 1);
}
pll cal(int L, int R) {
ll res1 = query_sum(L, R, 1, n, 1);
ll res2 = query_sq(L, R, 1, n, 1);
return MP(res1, res2);
}
int main() {
#ifdef ONLINE_JUDGE
#else
FIN;
#endif
scanf("%d%d", &n, &m);
for (int i = 1; i < n; i++) {
int u, v;
scanf("%d%d", &u, &v);
g[u].pb(v); g[v].pb(u);
}
for (int i = 1; i < m; i++) {
int a, b, u, v;
scanf("%d%d%d%d", &a, &b, &u, &v);
G[a].pb(MP(b, u));
G[b].pb(MP(a, v));
}
all = (ll)n * m % mod;
dfs(1, 0);
pre_solve();
build(1, n, 1);
for (int i = 1; i <= m; i++) {
for (auto now : G[i]) {
ll d = dep1[i] < dep1[now.fi] ? 1ll * SZ[now.fi] * n % mod : ((all % mod - 1ll * SZ[i] * n % mod) % mod + mod) % mod;
upd(1, now.se, d);
}
pll p = cal(2, n);
ans = (ans % mod + (all * p.fi % mod - p.se % mod) % mod + mod) % mod;
for (auto now : G[i]) {
ll d = dep1[i] < dep1[now.fi] ? 1ll * SZ[now.fi] * n % mod : ((all % mod - 1ll * SZ[i] * n % mod) % mod + mod) % mod;
upd(1, now.se, -d);
}
}
cout << ans << endl;
return 0;
}
div2版本:n,m<=1000
簡易題解:看了這個題,越發感覺div2是真的快樂,嘿嘿。div2版本按照題意建樹之後也只有1e6個節點,那就直接建樹即可。接着再用一個dfs維護一遍每個子樹的大小,就可以去算每條邊的貢獻了,每條邊的貢獻就是這條邊左側的節點個數×右側的節點個數。快樂的維護一下就行了。
參考代碼:
#include <bits/stdc++.h>
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;
#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<" "<<#z<<(z)<<"]\n"
typedef double db;
typedef long long ll;
typedef pair<int,int>pii;
typedef pair<ll,ll>pll;
const db eps = 1e-9;
const db pi = acos(-1.0);
const int MX = 1e6 + 5;
const int mod = 1e9 + 7;
//head
int n,m;
vector<int>G[MX];
int sz[MX];
ll ans = 0;
void dfs(int u,int fa){
sz[u] = 1;
for(auto v : G[u]){
if(v == fa) continue;
dfs(v,u);
sz[u] += sz[v];
ans = (ans % mod + (ll)sz[v] * (n * m - sz[v]) % mod) % mod;
}
}
int main(){
#ifdef LOCAL
FIN;
#endif
scanf("%d%d",&n,&m);
for(int i = 1;i < n;i++){
int u,v;
scanf("%d%d",&u,&v);
for(int j = 0;j < m;j++){
G[u+j*n].pb(v+j*n);
G[v+j*n].pb(u+j*n);
}
}
for(int i = 1;i < m;i++){
int a,b,u,v;
scanf("%d%d%d%d",&a,&b,&u,&v);
G[u+(a-1)*n].pb(v+(b-1)*n);
G[v+(b-1)*n].pb(u+(a-1)*n);
}
dfs(1,0);
cout << ans << endl;
return 0;
}
I.Sorting
這是一個div1和div2相同的題目。大概題意就是,給一個序列,有以下三種操作:
1、求區間[l,r]的和
2、對區間 [l,r] 進行如下操作:將所有小於等於x的數按原來的順序放到左邊,所有大於x的數按原來的順序放到右邊,再拼成一個新的序列放回區間[l,r]
3、對區間 [l,r] 進行如下操作:將所有小於等於x的數按原來的順序放到右邊,所有大於x的數按原來的順序放到左邊,再拼成一個新的序列放回區間[l,r]
簡易題解:對於2、3這兩種操作,我們可以發現不會如何進行操作,那些小於等於x的數的在序列內相對順序是不會變的,同理大於x的數也是一樣的。比如1 5 3 2 4 6,x = 3,對區間[2,5]進行操作2,序列就會變成1 3 2 5 4 6,而對於小於等於x的數,在進行操作前的順序是1 3 2,對於大於x的數原來的順序是 5 4 6,進行操作後也是一樣的。所以我們就可以把區間內小於等於x的數當做是0,大於x的數當做是1。這樣問題就轉換成了維護區間內0和1的位置關係。
每次操作2就變成了,將[l,l+num-1]內變成0(num爲區間內原來0的個數),將[l+num,r]內的數變成1;
操作3就變成了,將[l,l+num-1]內變成1(num爲區間內原來1的個數),將[l+num,r]內的數變成0;
查詢操作的話,就直接查詢區間[l,r]之間的0是從第幾個到第幾個的,(因爲相對順序是不會變的,所以我們可以直接維護一個前綴和),對於1也同理,這樣就可以直接用線段樹和前綴和去維護了。
參考代碼:
#include <bits/stdc++.h>
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int>pii;
#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x)<<" "<<#y<<" "<<(y)<<" "<<#z<<" "<<z<<"]\n"
const double pi = acos(-1.0);
const double eps = 1e-8;
const int MX = 2e5 + 5;
//head
int n, q, x;
int a[MX];
int cnt0, cnt1;
ll sum0[MX], sum1[MX];
int tree[MX << 2], tag[MX << 2];
void push_up(int rt) {
tree[rt] = tree[rt << 1] + tree[rt << 1 | 1];
}
void push_down(int l, int r, int rt) {
if (tag[rt] != -1) {
int m = (l + r) >> 1;
tag[rt << 1] = tag[rt << 1 | 1] = tag[rt];
tree[rt << 1] = (m - l + 1) * tag[rt];
tree[rt << 1 | 1] = (r - m) * tag[rt];
tag[rt] = -1;
}
}
void build(int l, int r, int rt) {
tag[rt] = -1;
tree[rt] = 0;
if (l == r) {
tree[rt] = (a[l] > x);
return;
}
int m = (l + r) >> 1;
build(lson);
build(rson);
push_up(rt);
}
void update(int L, int R, int d, int l, int r, int rt) {
if (L <= l && r <= R) {
tree[rt] = (r - l + 1) * d;
tag[rt] = d;
return;
}
push_down(l, r, rt);
int m = (l + r) >> 1;
if (L <= m) update(L, R, d, lson);
if (R > m) update(L, R, d, rson);
push_up(rt);
}
int query(int L, int R, int l, int r, int rt) {
if (L > R) return 0;
if (L <= l && r <= R) return tree[rt];
push_down(l, r, rt);
int m = (l + r) >> 1, res = 0;
if (L <= m) res += query(L, R, lson);
if (R > m) res += query(L, R, rson);
return res;
}
ll ask(int L, int R) {
int rb = query(1, R, 1, n, 1);
int lb = query(1, L - 1, 1, n, 1);
return (sum1[rb] - sum1[lb]) + (sum0[R - rb] - sum0[L - 1 - lb]);
}
int main() {
// FIN;
scanf("%d%d%d", &n, &q, &x);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
if (a[i] <= x) {
cnt0++;
sum0[cnt0] = sum0[cnt0 - 1] + a[i];
} else {
cnt1++;
sum1[cnt1] = sum1[cnt1 - 1] + a[i];
}
}
build(1, n, 1);
int op, l, r;
while (q--) {
scanf("%d%d%d", &op, &l, &r);
if (op == 1) printf("%lld\n", ask(l, r));
else if (op == 2) {
int num = query(l, r, 1, n, 1);
num = (r - l + 1) - num;
update(l, l + num - 1, 0, 1, n, 1);
update(l + num, r, 1, 1, n, 1);
} else {
int num = query(l, r, 1, n, 1);
update(l, l + num - 1, 1, 1, n, 1);
update(l + num, r, 0, 1, n, 1);
}
}
return 0;
}
J.Special Judge
這也是一個div1和div2相同的題目,div1的簽到題。突然感覺div2真的好快樂。
這個題就是給你一個圖的信息,再給你每個節點在二維平面中的座標信息,問你按原來的圖中的邊在二維平面中連邊會有多少對邊會相交(如果端點相同切交點只有端點就不算)
簡易題解:這就是一個簡單的幾何題。(雖然被題意殺了,隊友看錯了好久題目,後面還是靠着dls發的通知才悟到了真正的題意)。這就直接判線段相交就可以了,當然有一種特殊情況,就是兩條線段雖然是同端點,但是他們是兩條線段重合了的,用點積和叉積特判一下就可以了,叉積等於0,點積大於0就是這種情況,其他就是板子了。當然這個題座標比較大,用double的話可能會有浮點誤差,改成用long long就可以了。
參考代碼:
#include <bits/stdc++.h>
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define pb push_back
#define MP make_pair
#define rep(a,b) for(int i = a;i <= b;++i)
#define per(a,b) for(int i = b;i >= a;--i)
#define all(v) v.begin(),v.end()
#define clr(a) memset(a,0,sizeof(a))
#define FIN freopen("in.txt","r",stdin)
using namespace std;
#define debug1(x) cout<<"["<<#x<<" "<<(x)<<"]\n"
#define debug2(x,y) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<"]\n"
#define debug3(x,y,z) cout<<"["<<#x<<" "<<(x) << " " <<#y<<" "<<(y)<<" "<<#z<<(z)<<"]\n"
typedef long long ll;
typedef pair<int,int>pii;
typedef pair<ll,ll>pll;
//const ll eps = 1e-9;
//const ll pi = acos(-1.0);
const int MX = 2e5+7;
struct Point {
ll x, y;
Point() {}
Point(ll x,ll y):x(x),y(y) {}
};
typedef Point Vector;
int dcmp(ll x) { //返回x的正負
if(x == 0)return 0;
return x<0?-1:1;
}
Vector operator-(Vector A,Vector B) {return Vector(A.x - B.x, A.y - B.y);}
Vector operator+(Vector A,Vector B) {return Vector(A.x + B.x, A.y + B.y);}
Vector operator*(Vector A,ll p) {return Vector(A.x*p, A.y*p);}
Vector operator/(Vector A,ll p) {return Vector(A.x/p, A.y/p);}
bool operator<(const Point&a,const Point&b) {return a.x<b.x||(a.x==b.x&&a.y<b.y);}
bool operator==(const Point&a,const Point&b) {return dcmp(a.x-b.x)==0&&dcmp(a.y-b.y)==0;}
ll Dot(Vector A,Vector B) { //點積
return A.x*B.x+A.y*B.y;//如果改成整形記得加LL
}
ll Cross(Vector A,Vector B) { //叉積
return A.x*B.y-A.y*B.x;//如果改成整形記得加LL
}
//點p是否在線段a1a2上(不包含端點<,包含端點<=)
bool OnSegment(Point p,Point a1,Point a2) {
return dcmp(Cross(a1-p,a2-p))==0&&dcmp(Dot(a1-p,a2-p))<0;
}
bool SegmentProperIntersection(Point a1,Point a2,Point b1,Point b2) {
ll c1=Cross(a2-a1,b1-a1),c2=Cross(a2-a1,b2-a1),
c3=Cross(b2-b1,a1-b1),c4=Cross(b2-b1,a2-b1);
bool cnt = 0;
cnt |= OnSegment(a1,b1,b2);
cnt |= OnSegment(a2,b1,b2);
cnt |= OnSegment(b1,a1,a2);
cnt |= OnSegment(b2,a1,a2);
return ((dcmp(c1)^dcmp(c2))==-2&&(dcmp(c3)^dcmp(c4))==-2) | cnt;
}
pii vis[MX];
Point p[MX];
int main(){
#ifdef LOCAL
FIN;
#endif // LOCAL
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1,x,y; i<= m; i++){
scanf("%d%d",&x,&y);
vis[i].fi = x;
vis[i].se = y;
}
for(int i = 1; i <= n; i++){
int x,y;
scanf("%d%d",&x,&y);
p[i] = Point(x,y);
}
int ans = 0;
for(int i = 1; i <= m; i++){
for(int j = i+1; j <= m; j++){
if(vis[i].fi == vis[j].fi || vis[i].fi == vis[j].se || vis[i].se == vis[j].fi || vis[i].se == vis[j].se){
Point q,x,y;
if(vis[i].fi == vis[j].fi){
q = p[vis[i].fi];
x = p[vis[i].se];
y = p[vis[j].se];
}
if(vis[i].fi == vis[j].se){
q = p[vis[i].fi];
x = p[vis[i].se];
y = p[vis[j].fi];
}
if(vis[i].se == vis[j].fi){
q = p[vis[i].se];
x = p[vis[i].fi];
y = p[vis[j].se];
}
if(vis[i].se == vis[j].se){
q = p[vis[i].se];
x = p[vis[i].fi];
y = p[vis[j].fi];
}
if(Cross(q-x,q-y) == 0 && Dot(q-x,q-y) > 0) ans++;
continue;
}
if(SegmentProperIntersection(p[vis[i].fi],p[vis[i].se], p[vis[j].fi], p[vis[j].se])){
ans++;
}
}
}
cout<<ans<<endl;
return 0;
}