雅禮學習10.6
上午考試
各題狀況
T1
二分答案
應該有反例,就是說,答案應該不是單調的
但是不會寫其他的算法了啊。。。
T2
我TM。。。
第二個紅框圈出來的部分應該是
if(x1+x1!=s)
寫錯了,就沒了\(18\)分。。
T3
寫了個\(n^4\)的暴力
最後發現題目中的矩形的四個頂點不一定是給定的頂點。。
那就GG了
各題題目及考場代碼
T1
/*
* 二分答案。。
* 複雜度O(20(N+NlogN+M))的,感覺很懸
* 排序應該可以優化掉,但是不太會哎。
*/
#include <cstdio>
#include <algorithm>
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;
}
const int N=1e6+1;
int n,m;
struct Seg{
int b,k;
}seg[N];
long long S,emp[N];
inline bool judge(long long x)
{
for(int i=1;i<=n;++i)
emp[i]=seg[i].k*x+seg[i].b;
std::sort(emp+1,emp+1+n);
/*
for(int i=1;i<=n;++i)
printf("%lld ",emp[i]);
puts("");
*/
long long res=0;
for(int i=0;i<m;++i)
{
if(emp[n-i]<0)break;
res+=emp[n-i];
}
// printf("%lld\n",res);
return res>=S;
}
int main()
{
freopen("merchant.in","r",stdin);
freopen("merchant.out","w",stdout);
n=read(),m=read();
scanf("%lld",&S);
// printf("%d %d %lld\n",n,m,S);
for(int i=1;i<=n;++i)
{
seg[i].k=read(),seg[i].b=read();
// printf("%d %d\n",seg[i].k,seg[i].b);
}
int l=0,r=1e9,mid;
while(l<=r)
{
mid=l+r>>1;
if(judge(mid))
r=mid-1;
else l=mid+1;
}
printf("%d",l);
fclose(stdin),fclose(stdout);
return 0;
}
T2
/*
* 題目描述什麼鬼啊。。。完全看不懂
*
* emmmm
* 高斯消元麼。。。
*/
#include <cstdio>
#include <algorithm>
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;
}
const int N=1e6+1;
int n,q,fa[N],w[N];
/*
const double eps=1e-8;
double a[N][N];
int sign(double x)
{//判斷一個數是正數、負數還是0
if(fabs(x)<=eps)return 0;//fabs是對浮點數取絕對值
if(x>0)return 1;
return -1;
}
inline void GG(int x)
{
if(a[x][n+1])
puts("none");
else puts("inf");
}
inline void solve()
{
for(int i=1;i<=n;++i)
a[i][n+1]=b[i];//得到擴展矩陣
for(int i=1;i<=n;++i)
{
int p=i;
for(int j=i+1;j<=n;++j)
if(fabs(a[j][i]>fabs(a[p][i])))
p=j;
for(int j=1;j<=n+1;++j)
swap(a[p][j],a[i][j]);
if(sign(a[i][i])==0)
GG(i);
for(int j=i+1;j<=n;++j)
{
double ratio=a[j][i]/a[i][i];//計算是多少倍
for(int k=1;k<=n+1;++k)
a[j][k]=a[j][k]-ratio*a[i][k];
}
for(int i=n;i>0;--i)
{
for(int j=i+1;j<=n;++j)
a[i][n+1]=a[i][n+1]-x[j]*a[i][j];
x[i]=a[i][n+1]/a[i][i];
}
}
int main()
{
freopen("equation.in","r",stdin);
freopen("equation.out","w",stdout);
n=read(),q=read();
for(int i=2;i<=n;++i)
fa[i]=read(),w[i]=read();
int type,u,v,s;
while(q--)
{
type=read(),u=read(),v=read();
if(type==1)
{
s=read();
solve();
}
else
{
w[u]=v;
a[u][fa[u]]=v;
}
}
fclose(stdin);fclose(stdout);
return 0;
}
日。。
寫掛了
*/
int main()
{
freopen("equation.in","r",stdin);
freopen("equation.out","w",stdout);
n=read(),q=read();
for(int i=2;i<=n;++i)
fa[i]=read(),w[i]=read();
int type,u,v,s;
int x2,x1;
if(n==2)
{
while(q--)
{
type=read(),u=read(),v=read();
if(type==1)
{
s=read();
if(u==v && v==2)
{
x2=s/2;
if(x2+x2!=s)
puts("none");
else printf("%d\n",w[2]-x2);
}
else
if(u==v && v==1)
{
x1=s/2;
if(x2+x2!=s)
puts("none");
else printf("%d\n",x1);
}
else
{
if(s==w[2])
puts("inf");
else puts("none");
}
}
else w[u]=v;
}
}
else
{
while(q--)
{
srand(19260817);
while(q--)
{
int x=rand();
if(x%3==1)
puts("none");
else
if(x%3==2)
puts("inf");
else printf("%d\n",x);
}
}
}
fclose(stdin);fclose(stdout);
return 0;
}
T3
#include <cstdio>
#include <map>
const int N=2501,mod=1e9+7;
std::map<int,bool> mp1,mp2;
int n,t1,t2,lx,ly,rx,ry;
int ans,x[N],y[N];
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;}
inline int max(int x,int y)
{return x>y?x:y;}
int main()
{
freopen("rectangle.in","r",stdin);
freopen("rectangle.out","w",stdout);
n=read();
for(int i=1;i<=n;++i)
x[i]=read(),y[i]=read();
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
for(int q=1;q<=n;++q)
for(int p=1;p<=n;++p)
{
if(i==j&&j==q&&q==p)continue;
lx=min(min(x[i],x[j]),min(x[q],x[p]));
ly=min(min(y[i],y[j]),min(y[q],y[p]));
rx=max(max(x[i],x[j]),max(x[q],x[p]));
ry=max(max(y[i],y[j]),max(y[q],y[p]));
t1=(lx*ry+ly*rx);
t2=(lx*rx+ly*ry);
if(mp1[t1]==0&&mp2[t2]==0)
{
ans=(ans+(rx-lx)*(ry-ly))%mod;
mp1[t1]=mp2[t2]=1;
}
}
printf("%d",ans);
fclose(stdin);fclose(stdout);
return 0;
}
正解及代碼
T1
選擇任意一個集合,得到的收益和都可以表示爲一個一次函數的形式。
我們只關心這些一次函數的最大值,可以發現這個最大值一定是先降後增、單調遞增或者單調遞減。
因此我們只需要\(check\)一下\(0\)時刻是否符合條件,如果不符合則進行二分。 注意\(check\)的時候我們只需要找出最大的\(m\)個即可,因此可以\(O(n)\)地做,具體做法是快排的過程中只遞歸一邊,或者直接用\(STL\)的\(nth_element()\)即可
#include <bits/stdc++.h>
#define For(i, j, k) for (int i = j; i <= k; i++)
using namespace std;
const int N = 1e6 + 10;
typedef long long LL;
int n, m;
LL S;
int k[N], b[N];
LL val[N];
bool check(int x) {
For(i, 1, n) val[i] = 1ll * k[i] * x + b[i];
nth_element(val + 1, val + m, val + n + 1, greater<LL>());
LL sum = 0;
For(i, 1, m) if (val[i] > 0 && (sum += val[i]) >= S) return true;
return sum >= S;
}
int main() {
scanf("%d%d%lld", &n, &m, &S);
For(i, 1, n) scanf("%d%d", &k[i], &b[i]);
if (check(0)) { puts("0"); return 0; }
int L = 1, R = 1e9;
while (L < R) {
int mid = (L + R) / 2;
if (check(mid)) R = mid;
else L = mid + 1;
}
printf("%d\n", L);
return 0;
}
T2
每個變量都可以表示成\(x_i=k+x_1\)或者\(x_i=k−x_1\)的形式,表示爲這個形式之後就可以方便地回答詢問了。
對於詢問\(1\),只需要將表示\(u\)和\(v\)的式子加起來,這時會出現兩種情況:要麼會得到\(x_u+x_v=t\)的形式,此時只需要判斷是否有\(s=t\);要麼會得到\(x_u+x_v=t+2x_1\)或\(x_u+x_v=t−2x_1\),此時可以解出\(x_1\),注意判斷解是否是整數即可。
對於修改操作,實際上是修改一個子樹內的變量的\(k\),這裏可以將深度爲奇數和偶數的點分開考慮,不難發現就是區間加減。由於只需要單點詢問,用一個樹狀數組維護即可
\(O((n+q)\log n)\)
#include <bits/stdc++.h>
#define getchar getchar_unlocked
#define For(i, j, k) for (int i = j; i <= k; i++)
using namespace std;
int Read() {
char c = getchar(); int x = 0;
int sig = 1;
while (c < '0' || c > '9') { if (c == '-') sig = -1; c = getchar(); }
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * sig;
}
const int N = 1e6 + 10;
int n, q;
struct Binary_Indexed_Tree {
int c[N];
inline int lowbit(int x) {
return x & (-x);
}
void add(int x, int w) {
while (x <= n) {
c[x] += w;
x += lowbit(x);
}
}
int sum(int x) {
int ret = 0;
while (x) {
ret += c[x];
x -= lowbit(x);
}
return ret;
}
}T;
int dfn[N], rdfn[N], dep[N];
int fa[N], w[N];
vector<int> G[N];
void DFS_init(int o) {
static int clk = 0;
dfn[o] = ++clk;
for (int v : G[o]) dep[v] = dep[o] ^ 1, DFS_init(v);
rdfn[o] = clk;
}
int main() {
n = Read(), q = Read();
For(i, 2, n) fa[i] = Read(), w[i] = Read(), G[fa[i]].push_back(i);
DFS_init(1);
For(i, 2, n) if (!dep[i]) w[i] = -w[i];
For(i, 2, n) T.add(dfn[i], w[i]), T.add(rdfn[i] + 1, -w[i]);
while (q--) {
int op = Read();
if (op == 1) {
int u = Read(), v = Read(), s = Read();
int x = T.sum(dfn[u]), y = T.sum(dfn[v]);
if (dep[u] && dep[v]) {
long long rt = 1ll * x + y - s;
if (rt % 2) puts("none");
else printf("%lld\n", rt / 2);
} else if (!dep[u] && !dep[v]) {
long long rt = 1ll * x + y + s;
if (rt % 2) puts("none");
else printf("%lld\n", rt / 2);
} else {
if (dep[v]) swap(u, v), swap(x, y);
if (x - y == s) puts("inf");
else puts("none");
}
} else {
int u = Read(), nw = Read();
if (!dep[u]) nw = -nw;
T.add(dfn[u], nw - w[u]), T.add(rdfn[u] + 1, w[u] - nw);
w[u] = nw;
}
}
return 0;
}
T3
#include <bits/stdc++.h>
#define For(i, j, k) for (int i = j; i <= k; i++)
#define Forr(i, j, k) for (int i = j; i >= k; i--)
using namespace std;
const int N = 2510;
const int Mod = 1e9 + 7;
int n, m = 2500;
struct Binary_Indexed_Tree {
int c[N];
void init() {
For(i, 1, m) c[i] = 0;
}
int lowbit(int x) { return x & (-x); }
void add(int x, int w) {
for (; x <= m; x += lowbit(x)) c[x] += w;
}
int sum(int x) {
int ret = 0;
for (; x; x -= lowbit(x)) ret += c[x];
return ret;
}
}T, S;
int pos[N][N];
int c[N];
bool vis[N];
void upd(int x) {
if (vis[x]) return;
vis[x] = true;
T.add(x, 1), S.add(x, x);
}
int main() {
scanf("%d", &n);
For(i, 1, n) {
int x, y;
scanf("%d%d", &x, &y);
pos[x][++c[x]] = y;
}
For(i, 1, m) sort(pos[i] + 1, pos[i] + c[i] + 1), pos[i][c[i] + 1] = m + 1;
int ans = 0;
For(i, 1, m) if (c[i]) {
T.init(), S.init();
For(j, 1, m) vis[j] = false;
For(j, 1, c[i]) upd(pos[i][j]);
Forr(j, i - 1, 1) if (c[j]) {
int pa = 1, pb = 1, cur = max(pos[i][1], pos[j][1]);
For(k, 1, c[j]) upd(pos[j][k]);
while (pos[i][pa + 1] <= cur) ++pa;
while (pos[j][pb + 1] <= cur) ++pb;
while (pa <= c[i] && pb <= c[j]) {
int nxt = min(pos[i][pa + 1], pos[j][pb + 1]), L = min(pos[i][pa], pos[j][pb]);
ans = (ans + (1ll * (S.sum(nxt - 1) - S.sum(cur - 1)) * T.sum(L)
- 1ll * (T.sum(nxt - 1) - T.sum(cur - 1)) * S.sum(L)) * (i - j)) % Mod;
cur = nxt;
if (pos[i][pa + 1] <= cur) ++pa;
if (pos[j][pb + 1] <= cur) ++pb;
}
}
}
printf("%d\n", ans);
return 0;
}
下午講課:DP選講
例1
一個長度爲\(n\)的序列\(A\),我們稱一個元素是好的,當且僅當它嚴格大於相鄰的元素。你可以進行若干次操作,每次將一個元素減小\(1\)
對於每個\(k\in [1,\lceil\frac{n}{2}\rceil]\)求至少要進行多少次操作使得序列中至少有\(k\)個好的元素
\(n\le 5000,A_i\le 10^5\)
解:
稍作觀察發現,如果最終方案中一個位置是好的,那我們一定不會對它做操作;如果不是,它最終的值是\(A_{i-1}-1,A_i,A_{i+1}-1\)中的一個
令\(dp[i][j][0]\)表示前\(i\)個元素有\(j\)個是好的,且已經欽定\(A_i\)是好的,此
時對前\(i\)個元素至少要進行的操作次數。轉移到\(i+1\)時需要確保操作後\(A_{i+1}\lt A_i\)
類似地,\(dp[i][j][1/2/3]\)表示\(A_i\)不是好的時的三種情況
\(O(n^2)\)
例2
一個長度爲\(n\)的序列\(A\),定義一個1到n的排列p是合法的,當且僅當\(\forall i\in[1,n-1].A_{p_i}\times A_{p_{i+1}}\)不是完全平方數
求有多少合法的排列,對\(1e9+7\)取模
\(n\le 300,A_i\le 10^9\)
解:
對於每個元素去掉它的平方質因子,問題轉化爲有多少排列\(p\)滿足\(\forall i\in[1,n-1],A_{p_i}\ne A_{p_{i+1}}\),即相鄰元素不同
先統計有多少種不同的元素,以及每種元素的個數。考慮每次將值相同的所有元素加入排列後的序列
設\(dp[i][j]\)表示已經將前\(i\)種元素加入序列,有\(j\)對相鄰位置相同的方案數。轉移時枚舉將第\(i+1\)種元素加入後會將多少對原來不合法的相鄰位置拆開,以及會新增多少不合法的相鄰位置即可。
總元素個數\(O(n)\),因此複雜度最壞\(O(n^3)\)
例3
有一個長度爲\(n\)的序列\(A\)和常數\(L,P\),你需要將它分成若干段,每一段的代價爲\(|(\sum A_i)-L|^P\),求最小代價的劃分方案
\(n\le 10^5,1\le P\le 10\)
解:
\[
dp[j]=\min_{i=0}^{j-1}|sum_j-sum_i-L|^P+dp[i]
\]
這個方程具有決策單調性,即\(\forall u\lt v\lt i\lt j\),若在\(i\)處決策\(v\)優於決策\(u\),則在\(j\)處必有\(v\)優於決策\(u\)
用一個棧維護每個決策更新的區間,新加入一個決策時可以二分得到它的區間
\(O(n\log n)\)
證明:
本題的證明需要討論絕對值符號,先考慮裏面的值爲正的情況,其餘的情況類似。
定義\(f_i(x)=dp[i]+(sum_x-sum_i-L)^P\),只需證明\(g(x)=f_u(x)-f_v(x),u\lt v\lt x\)單調增
也就是隨着\(x\)增大決策\(u\)相較於決策\(v\)越來越不優
\[ g(x)=(sum_x-sum_u)^P-(sum_x-sum_v)^P+dp[u]-dp[v]\\ g'(x)=P(sum_x-sum_u)^{P-1}-P(sum_x-sum_v)^{P-1}\\ g'(x)\ge 0 \]
例4
一張\(n\)個點\(m\)條邊的帶權有向圖,每條邊的長度都爲\(1\)。求一條最長的路徑,滿足邊權嚴格遞增,且路徑上邊的順序與輸入中這些邊的相對順序相同。
\(n,m\le 10^5\)
解:
按輸入順序考慮每一條邊。設\(dp[i][j]\)表示路徑到了節點\(i\),上一條邊的權值\(j\)時的最長長度。
顯然有用的狀態是\(O(m)\)的。對每個點維護一個\(set\)來存這些狀態以及它們的\(dp\)值,並保證\(dp\)值是隨\(j\)遞增的。這樣加入一條邊\((u,v)\)時,只需要在節點\(u\)的\(set\)上二分就可以進行轉移了。同時還需要維護\(v\)的\(set\)。
\(O(n+m\log m)\)
例5
有一棵\(n\)個點的帶權二叉樹(不知道樹的形態),給出對這棵二叉樹進行中序遍歷得到的權值序列,判斷是否存在與之相符的一棵二叉樹,樹上每對相鄰節點權值的\(gcd\)大於\(1\)
\(n\le 700,w_i\le 10^9\)
解
對於二叉樹的一棵子樹,其中序遍歷是一段連續的區間。一個想法(跟昨天的題目很像)是設\(dp[l][r][i]\)表示是否能將區間\([l,r]\)建成一棵以\(i\)爲根的合法二叉樹,轉移枚舉兩邊子樹的根,但這樣的複雜度是\(O(n^5)\)
注意到對於區間\([l,r]\)構成的二叉樹,除非\(l=1,r=n\),否則它一定是\(r+1\)的左子樹,或者\(l-1\)的右子樹。因此我們只關心根節點與\(l-1\)和\(r+1\)的權值的\(gcd\)是否爲\(1\),而不需要知道根是哪個節點。
於是狀態數變爲\(O(n^2)\),轉移仍然枚舉根即可,\(O(n^3)\)