题目顺序是:
T1 T2 T3 T4 T5
368 369 370 365 364
题目顺序是:
T1 T2 T3 T4 T5
368 369 370 365 364
import asyncio
import time
import websockets
import aiohttp
import traceback
import json
import random
import base64
import aiosqlite
import re
import os
wsserver = 'ws://172.17.0.2:3001/'
ws = None
commands = dict ()
trusted_senders = [ 2681243014 ]
confirm_queue = dict ()
reply_queue = dict ()
file_queue = dict ()
sqlconn = None
GROUP_ID = 1061173220
def wyoj_enable (qq, name):
if re.match (r'^([A-Za-z0-9_]*)$', name) is None:
raise Exception ('wyoj_enable: Invalid username; possible injection')
os.system (f"sudo docker exec uoj-db mysql -proot app_uoj233 -e \"update user_info set usergroup = 'U', qq = '{qq}' where username = '{name}'\"")
def wyoj_disable (name):
if re.match (r'^([A-Za-z0-9_]*)$', name) is None:
raise Exception ('wyoj_disable: Invalid username; possible injection')
os.system (f"sudo docker exec uoj-db mysql -proot app_uoj233 -e \"update user_info set usergroup = 'B' where username = '{name}'\"")
def wyoj_adminize (name):
if re.match (r'^([A-Za-z0-9_]*)$', name) is None:
raise Exception ('wyoj_adminize: Invalid username; possible injection')
os.system (f"sudo docker exec uoj-db mysql -proot app_uoj233 -e \"update user_info set usergroup = 'S' where username = '{name}'\"")
# 暂时不考虑嵌套。
def wyoj_deadminize (name):
if re.match (r'^([A-Za-z0-9_]*)$', name) is None:
raise Exception ('wyoj_deadminize: Invalid username; possible injection')
os.system (f"sudo docker exec uoj-db mysql -proot app_uoj233 -e \"update user_info set usergroup = 'U' where username = '{name}'\"")
def wyoj_admins ():
return os.popen ("sudo docker exec uoj-db mysql -proot app_uoj233 -e \"select username from user_info where usergroup = 'S';\"").read ()
def wyoj_banned ():
return os.popen ("sudo docker exec uoj-db mysql -proot app_uoj233 -e \"select username from user_info where usergroup = 'B';\"").read ()
async def db_insert_bind (uid, name):
cursor = await sqlconn.cursor ()
await cursor.execute (f'insert into binds (qq, name) values (\'{uid}\', \'{name}\')');
await sqlconn.commit ()
await cursor.close ()
async def db_delete_bind (name):
cursor = await sqlconn.cursor ()
await cursor.execute (f'delete from binds where name = \'{name}\'');
await sqlconn.commit ()
await cursor.close ()
async def db_query_by_id (uid):
cursor = await sqlconn.cursor ()
await cursor.execute (f'select qq, name from binds where qq = \'{uid}\'')
res = await cursor.fetchall ()
await cursor.close ()
return res
async def db_query_by_name (name):
cursor = await sqlconn.cursor ()
await cursor.execute (f'select qq, name from binds where name = \'{name}\'')
res = await cursor.fetchall ()
await cursor.close ()
if len (res) > 1:
raise Exception ('重复绑定')
if len (res) == 1:
return res[0]
return None
async def db_query_binds ():
cursor = await sqlconn.cursor ()
await cursor.execute (f'select qq, name from binds order by qq')
res = await cursor.fetchall ()
await cursor.close ()
return res
async def db_count_bound (qq):
cursor = await sqlconn.cursor ()
await cursor.execute (f'select count (*) from binds where qq = \'{qq}\'')
res = await cursor.fetchone ()
await cursor.close ()
return res[0]
async def db_check_bound (name):
cursor = await sqlconn.cursor ()
await cursor.execute (f'select count (*) from binds where name = \'{name}\'')
res = await cursor.fetchone ()
await cursor.close ()
return res[0] > 0
async def db_banned (qq):
cursor = await sqlconn.cursor ()
await cursor.execute (f'select count (*) from bans where qq = \'{qq}\'')
res = await cursor.fetchone ()
await cursor.close ()
return res[0] > 0
async def db_banned_all ():
cursor = await sqlconn.cursor ()
await cursor.execute (f'select qq from bans')
res = await cursor.fetchall ()
await cursor.close ()
return res
async def db_ban (qq):
cursor = await sqlconn.cursor ()
await cursor.execute (f'insert into bans (qq) values (\'{qq}\')');
await sqlconn.commit ()
await cursor.close ()
async def db_unban (qq):
cursor = await sqlconn.cursor ()
await cursor.execute (f'delete from bans where qq = \'{qq}\'');
await sqlconn.commit ()
await cursor.close ()
refpatt = re.compile (r'\[CQ:reply,id=([0-9]*)\]')
atpatt = re.compile (r'\[CQ:at,qq=([0-9]*)\]')
class MessageMeta:
def __init__ (self, data):
s = data['raw_message']
self.data = data
self.uid = data['user_id']
self.gid = data['group_id']
self.msgid = data['message_id']
self.refs = re.findall (refpatt, s)
self.ats = re.findall (atpatt, s)
i = 0
flag = False
while i < len (s) and (flag or s[i] == '['):
flag |= s[i] == '['
flag &= s[i] != ']'
i += 1
while i < len (s) and s[i] == ' ':
i += 1
self.msg = s[i:]
self.cmd = self.msg.split ()
async def reply_msg (meta, msg):
return await ws.send (json.dumps ({
'action': 'send_group_msg',
'params': {
'group_id': meta.gid,
'message': [ { 'type': 'reply', 'data': { 'id': meta.msgid } },
{ 'type': 'text', 'data': { 'text': msg } } ]}}))
def download_dir (s):
return f'./download/{s}'
FILE_KEEP_MIN = 60
REPOTER_GAP = 5
CHUNK_SIZE = 1048576 * 16
async def receive_file (meta, data):
size = int (data['message'][0]['data']['file_size'])
url = data['message'][0]['data']['url']
name = data['message'][0]['data']['file']
if not name.endswith ('.zip'):
return await reply_msg (meta, f'识别到非 ZIP 格式文件,未下载')
await reply_msg (meta, f'识别到 ZIP 格式文件,大小 %.3lf MB 正在下载' % (size / 1048576))
start_time = time.time ()
rx = 0
finished = False
async def reporter ():
while not finished:
duration = time.time () - start_time
if rx != 0:
await reply_msg (meta, f'已下载 %.3lf MB = %.3lf MB/s = %.3lf Mbps, ETA %.2lf 秒'
% (rx / 1048576, rx / 1048576 / duration, rx * 8 / 1048576 / duration, duration / rx * size))
await asyncio.sleep (REPOTER_GAP)
asyncio.create_task (reporter ())
async with aiohttp.ClientSession () as sess:
async with sess.get (url) as resp:
if resp.status != 200:
await reply_msg (meta, f'请求 {url} 失败。HTTP 状态码 {resp.status}')
filename = download_dir (str (meta.msgid))
with open (filename, 'wb') as f:
async for chunk in resp.content.iter_chunked (CHUNK_SIZE):
f.write (chunk)
rx += len (chunk)
duration = time.time () - start_time
finished = True
await reply_msg (meta, f'已成功下存到 {filename},耗时 %.3lf 秒,合 %.3lf MB/s = %.3lf Mbps\n将在 {FILE_KEEP_MIN} 分钟后删除。'
% (duration, size / duration / 1048576, size * 8 / 1048576 / duration))
await asyncio.sleep (FILE_KEEP_MIN * 60)
await reply_msg (meta, f'到时,删除文件 {filename}')
os.system (f'rm {download_dir (filename)}')
def random_token ():
s = ''
for i in range (16):
s += random.choice ('abcdefghijklmnopqrstuvwxyz')
return s
def gen_confirm_token (meta):
while True:
s = str (meta.uid) + random_token ()
s = base64.b64encode (s.encode ('utf-8')).decode ('utf-8')
if s not in confirm_queue:
return s
CONFIRM_TIMEOUT = 60
async def confirm (meta, msg):
token = gen_confirm_token (meta)
future = asyncio.Future ()
confirm_queue[token] = future
await reply_msg (meta, f'{msg}\n{CONFIRM_TIMEOUT} 秒内 /confirm {token} 来确认')
try:
result = await asyncio.wait_for (future, timeout=CONFIRM_TIMEOUT)
except asyncio.TimeoutError:
await reply_msg (meta, f'超时,取消操作')
result = False
else:
await reply_msg (meta, f'成功确认')
finally:
del confirm_queue[token]
return result
def register_cmd (cmd):
def decorator (fn):
if cmd in commands:
raise Exception(f'试图第二次注册命令 {cmd}')
commands[cmd] = fn
def wrapper (*args, **kwargs):
return fn (*args, **kwargs)
return wrapper
return decorator
def trusted_sender (fn):
async def wrapper (*args, **kwargs):
meta = args[0]
if meta.uid in trusted_senders:
return await fn (*args, **kwargs)
return await reply_msg (args[0], f'执行该命令的权限不足')
return wrapper
@register_cmd ('/confirm')
async def cmd_confirm (meta, arg):
if len (arg) != 2:
return await reply_msg (meta, f'需要令牌')
token = arg[1]
if token not in confirm_queue:
return await reply_msg (meta, f'无效令牌')
uid = base64.b64decode (token).decode ('utf-8')[:-16]
if uid != str (meta.uid):
return await reply_msg (meta, f'错误的确认者。期望 {uid},得到 {meta.uid}')
confirm_queue[token].set_result (True)
@register_cmd ('/testre')
async def cmd_testre (meta, arg):
res = await wait_reply (meta, f'请回复这条消息')
if not res:
await reply_msg (meta, '为啥不回复?')
await reply_msg (meta, f'你说:{' '.join (res)}')
@register_cmd ('/ping')
async def cmd_ping (meta, arg):
s = f'PONG {meta.uid}'
if meta.uid in trusted_senders:
s += ' (privileged)'
if await db_banned (meta.uid):
s += ' (banned)'
await reply_msg (meta, s)
MAX_BINDS = 3
@register_cmd ('/bind')
async def cmd_bind (meta, arg):
if len (arg) != 2:
return await reply_msg (meta, f'需要 WyOJ 用户名\nUsage: /bind username')
name = arg[1]
if re.match (r'^([A-Za-z0-9_]*)$', name) is None:
return await reply_msg (meta, '不合法用户名')
if not await confirm (meta, f'确认将 {meta.uid} 绑定到 {name} 吗?'):
return
if await db_check_bound (name):
qq, _ = await db_query_by_name (name)
return await reply_msg (meta, f'无法绑定 {name};已绑定到 {qq}')
if await db_count_bound (meta.uid) >= MAX_BINDS:
return await reply_msg (meta, f'无法绑定更多账号。单个 QQ 绑定最大值为 {MAX_BINDS}')
await db_insert_bind (meta.uid, name)
wyoj_enable (meta.uid, name)
await reply_msg (meta, f'成功将 {name} 绑定到 {meta.uid}')
@register_cmd ('/rebind')
@trusted_sender
async def cmd_rebind (meta, arg):
if len (arg) != 2:
return await reply_msg (meta, f'需要 WyOJ 用户名\nUsage: /bind username')
name = arg[1]
if re.match (r'^([A-Za-z0-9_]*)$', name) is None:
return await reply_msg (meta, '不合法用户名')
if not await db_check_bound (name):
return await reply_msg (meta, f'无绑定')
qq, _ = await db_query_by_name (name)
await db_delete_bind (name)
await db_insert_bind (qq, name)
wyoj_enable (qq, name)
await reply_msg (meta, f'已重新绑定')
@register_cmd ('/refresh')
@trusted_sender
async def cmd_refresh (meta, arg):
q = await db_query_binds ()
s = ''
cnt = 0
for qq, name in q:
await db_delete_bind (name)
await db_insert_bind (qq, name)
wyoj_enable (qq, name)
s += f'{qq}\t\t{name}\n'
cnt += 1
await reply_msg (meta, f'已成功绑定:\n{s}\n计 {cnt} 人')
@register_cmd ('/unbind')
async def cmd_unbind (meta, arg):
if len (arg) != 2:
return await reply_msg (meta, f'需要 WyOJ 用户名\nUsage: /unbind username')
name = arg[1]
if re.match (r'^([A-Za-z0-9_]*)$', name) is None:
return await reply_msg (meta, '不合法用户名')
if not await db_check_bound (name):
return await reply_msg (meta, '该账号未绑定')
qq, _ = await db_query_by_name (name)
if qq != str (meta.uid):
if meta.uid in trusted_senders:
await reply_msg (meta, f'你是管理员 {meta.uid},强制解绑,等待确认')
else:
return await reply_msg (meta, f'{name} 与 {qq} 的绑定无法由你 ({meta.uid}) 解绑')
if not await confirm (meta, f'确认将 {meta.uid} 从 {name} 解绑吗?'):
return
if not await db_check_bound (name):
return await reply_msg (meta, f'尚无绑定,无法解绑')
await db_delete_bind (name)
wyoj_disable (name)
await reply_msg (meta, f'成功将 {meta.uid} 与 {name} 解绑')
@register_cmd ('/adminize')
@trusted_sender
async def cmd_adminize (meta, arg):
if len (arg) not in [ 2, 3 ]:
return await reply_msg (meta, f'需要用户名及时间\nUsage: /adminize username [time-in-minutes]')
name = arg[1]
if re.match (r'^([A-Za-z0-9_]*)$', name) is None:
return await reply_msg (meta, '不合法用户名')
time = -1
if len (arg) == 3:
try:
time = int (arg[2])
except ValueError:
return await reply_msg (meta, f'不合法的时间 {arg[2]}')
if not await confirm (meta, f'确定要赋予 {name} 以 {time} 分钟的管理员权限吗?'):
return
wyoj_adminize (name)
await reply_msg (meta, f'{name} 已被赋予管理员权限')
if time > 0:
await asyncio.sleep (time * 60)
wyoj_deadminize (name)
await reply_msg (meta, f'{name} 的管理员权限已到期')
@register_cmd ('/deadminize')
@trusted_sender
async def cmd_deadminize (meta, arg):
if len (arg) not in [ 2, 3 ]:
return await reply_msg (meta, f'需要用户名\nUsage: /deadminize username')
name = arg[1]
if re.match (r'^([A-Za-z0-9_]*)$', name) is None:
return await reply_msg (meta, '不合法用户名')
if not await confirm (meta, f'确定要取消 {name} 的管理员权限吗?'):
return
wyoj_deadminize (name)
await reply_msg (meta, f'{name} 已被赋予管理员权限')
async def cmd_query_qq (meta, arg):
qq = arg[2]
try:
int (qq)
except ValueError:
await reply_msg (meta, f'无效 QQ 号 {qq}')
res = await db_query_by_id (qq)
await reply_msg (meta, f'QQ {qq} 绑定到 {len (res)} 个账号:{", ".join (map (lambda x: x[1], res))}')
async def cmd_query_me (meta, arg):
arg.append ('qq')
arg.append (str (meta.uid))
await cmd_query_qq (meta, arg)
async def cmd_query_name (meta, arg):
name = arg[2]
if re.match (r'^([A-Za-z0-9_]*)$', name) is None:
return await reply_msg (meta, '不合法用户名')
res = await db_query_by_name (name)
if not res:
await reply_msg (meta, f'{name} 未绑定')
else:
await reply_msg (meta, f'{name} 绑定到 {res[0]}')
@trusted_sender
async def cmd_query_all (meta, arg):
await reply_msg (meta, f'绑定表:\n{"\n".join (map (lambda x: x[0] + "\t\t" + x[1], await db_query_binds ()))}')
@trusted_sender
async def cmd_query_banned (meta, arg):
await reply_msg (meta, wyoj_banned ())
@trusted_sender
async def cmd_query_admins (meta, arg):
await reply_msg (meta, wyoj_admins ())
@register_cmd ('/query')
async def cmd_query (meta, arg):
if len (arg) == 1:
return await cmd_query_me (meta, arg)
elif len (arg) == 2:
if arg[1] == 'all':
return await cmd_query_all (meta, arg)
elif arg[1] == 'banned':
return await cmd_query_banned (meta, arg)
elif arg[1] == 'admins':
return await cmd_query_admins (meta, arg)
elif len (arg) == 3:
if arg[1] == 'qq':
return await cmd_query_qq (meta, arg)
elif arg[1] == 'name':
return await cmd_query_name (meta, arg)
await reply_msg (meta, '期望零个或两个参数\nUsage: /query || /query qq qqid || /query name username')
@register_cmd ('/ban')
@trusted_sender
async def cmd_ban (meta, arg):
if len (arg) != 2:
return await reply_msg (meta, f'需要一个参数\nUsage: /ban username')
try:
qq = str (int (arg[1]))
except ValueError:
return await reply_msg (meta, f'无效 QQ 号 {qq}')
if not await confirm (meta, f'确定要封禁 {qq} 及其名下的所有账号吗?'):
return
await db_ban (qq)
res = await db_query_by_id (qq)
for _, name in res:
wyoj_disable (name)
await reply_msg (meta, f'被封禁的用户:{", ".join (map (lambda x: x[1], res))}')
@register_cmd ('/unban')
@trusted_sender
async def cmd_unban (meta, arg):
if len (arg) != 2:
return await reply_msg (meta, f'需要一个参数\nUsage: /unban username')
try:
qq = str (int (arg[1]))
except ValueError:
return await reply_msg (meta, f'无效 QQ 号 {qq}')
if not await confirm (meta, f'确定要解封 {qq} 吗(不解封账号)?'):
return
await db_unban (qq)
await reply_msg (meta, '成功解封')
@register_cmd ('/banlist')
async def cmd_banlist (meta, arg):
await reply_msg (meta, f'被封禁的 QQ:{", ".join (map (lambda x: x[0], await db_banned_all ()))}')
@register_cmd ('/entrust')
@trusted_sender
async def cmd_entrust (meta, arg):
global trusted_senders
if len (meta.ats) == 0:
await reply_msg (meta, f'需要 At 某人\nUsage: [@someone..] /entrust [@someone..]')
if not await confirm (meta, f'确定信任 {' '.join (meta.ats)} 吗?'):
return
for i in meta.ats:
try:
int (i)
except ValueError:
return await reply_msg (meta, f'非法 At')
trusted_senders += map (int, meta.ats)
await reply_msg (meta, f'成功信任')
@register_cmd ('/detrust')
@trusted_sender
async def cmd_detrust (meta, arg):
global trusted_senders
if len (meta.ats) == 0:
await reply_msg (meta, f'需要 At 某人\nUsage: [@someone..] /entrust [@someone..]')
if not await confirm (meta, f'确定取消信任 {' '.join (meta.ats)} 吗?'):
return
for i in meta.ats:
try:
int (i)
except ValueError:
return await reply_msg (meta, f'非法 At')
if int (i) not in trusted_senders:
await reply_msg (meta, '其实 {i} 并未被信任。')
for i in meta.ats:
trusted_senders.remove (int (i))
await reply_msg (meta, f'成功取消信任')
@register_cmd ('/trusts')
async def cmd_trusts (meta, arg):
await reply_msg (meta, f'信任用户:{' '.join (map (str, trusted_senders))}')
async def run_cmd (cmd):
proc = await asyncio.create_subprocess_shell (
cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE)
stdout, stderr = await proc.communicate ()
return stdout.decode ('utf-8'), stderr.decode ('utf-8')
@register_cmd ('/upload')
@trusted_sender
async def cmd_upload (meta, arg):
pid = arg[1]
try:
pid = int (pid)
except ValueError:
return await reply_msg (meta, f'非法题号')
if len (meta.refs) != 1:
return await reply_msg (meta, f'需要有且仅有一个指向题目数据压缩包的引用')
if not os.path.exists (download_dir (meta.refs[0])):
return await reply_msg (meta, f'数据文件未被下载:错误的引用或者已经超时')
await reply_msg (meta, f'正在上传数据')
stdout, stderr = await run_cmd (f'/home/ryp/upload {pid} {download_dir (meta.refs[0])}')
await reply_msg (meta, f'命令执行完毕。标准输出:\n{stdout}\n\n标准错误:{stderr}\n' \
f'若无错误,请打开 https://oj.ryp.org.cn/problem/{pid}/manage/data 点击校验配置并同步数据')
async def dispatch (meta):
if not meta.cmd[0] in commands:
return await reply_msg (meta, f'未知命令 {meta.cmd[0]}')
await commands[meta.cmd[0]] (meta, meta.cmd)
async def handle_msg (data):
try:
data = json.loads (data)
meta = MessageMeta (data)
if meta.gid != GROUP_ID:
return
if data['message'][0]['type'] == 'file':
return await receive_file (meta, data)
if meta.msg[0] != '/':
return
print (f'收到群 {meta.gid} 的用户 {meta.uid} 的命令:{' '.join (meta.cmd)}')
await dispatch (meta)
except KeyError as e:
pass
except Exception as e:
print (f'未知错误:{e}')
traceback.print_exc ()
async def client_loop ():
global ws, sqlconn
ws = await websockets.connect (wsserver)
sqlconn = await aiosqlite.connect ('data.db')
print ('已成功连接到 NapCat 服务器')
async for msg in ws:
asyncio.create_task (handle_msg (msg))
if __name__ == '__main__':
asyncio.run (client_loop ())
import tkinter as tk
import tkinter.simpledialog
from tkinterdnd2 import DND_FILES, TkinterDnD
rt = TkinterDnD.Tk ()
rt.title ('WyOJ 数据配置工具')
def ask_subtask_nr ():
n = tk.simpledialog.askinteger (title='Subtask 数量',
prompt='要配置的 Subtask 数量',
initialvalue=0)
print (f'你想配置 {n} 个 Subtask')
return n
def configure_subtask (i):
def config ():
win = tk.Toplevel (rt)
win.title (f'配置 Subtask {i}')
droparea = tk.Label (win,
text='>>> 请将本 Subtask 中的测试点拖到这里 <<<',
height=10)
txt = tk.Text (win)
txt.pack ()
def drop (ev):
fp = ev.data
txt.insert (tk.END, f'{fp}\n')
droparea.drop_target_register (DND_FILES)
droparea.dnd_bind ('<<Drop>>', drop)
droparea.pack ()
txt.pack ()
return config
buttons = list ()
def create_subtask_buttons (nr):
for i in range (1, nr + 1):
butt = tk.Button (rt, text=f'配置 Subtask {i}',
command=configure_subtask (i))
butt.place (x=80, y=20 + i * 40, width=60, height=30)
butt.pack ()
buttons.append (butt)
nr = ask_subtask_nr ()
create_subtask_buttons (nr)
rt.mainloop ()
为了减少完全以刷号为意义的小号出现,并且将线上可能出现的不恰当行为言论与线下相关联,我们决定 任何账号都必须绑定到 QQ 以进行活动。
具体方法:
加入 QQ 群 1061173220,口令是 wyojyydsrypnb
在群中发消息 /bind XXX,其中 XXX 是你的用户名
WyOJ Musume,也就是 WyOJ 的机器人,会回应你的消息。
根据 WyOJ Musume 的指示来完成操作,绑定成功后,你的账号便可用。
注意:
每个账号只能绑定一个 QQ,但一个 QQ 可以绑定最多三个账号。
若某个账号决定被封禁,其对应的 QQ 会被封禁,对应 QQ 下的所有账号也会被封禁。
递推,比正解还麻烦,略。
超级笨蛋博弈论,找规律。
考虑倒推,剩 $1$ 到 $m$ 张是先手有必胜策略,$m+1$ 是后手有必胜策略。
继续,考虑 $m+2$ 到 $2m+1$,先手方可以将纸牌数控制为 $m+1$,使得对方从 $m+1$ 开始取,进而自己有必胜策略;若 $2m+2$,则不论如何取,剩下纸牌在 $[m+2,2m+1]$ 范围内,后手有必胜策略。
归纳可知,若初始纸牌数 $n$ 为 $m+1$ 的倍数,后手有必胜策略,否则先手有必胜策略。
$n\le 10$,乱搞点暴搜之类的东西大概就可以过的吧(其实我也不知道有什么具体的办法)。
$n\le 10^3$,是不加优化的 DP 做法。
问题的一个难解决的地方就在于分组的实质是选数并建立集合,而不是选取某一段。但要使每个集合中的最大值减最小值(集合即每一组,值即体积,下同)尽可能小,每个集合的元素就一定是排序后的数中某连续的一段,于是直接按物品体积进行排序,这样就可以把集合问题转化为分段问题了。
设 $f_i$ 表示将前 $i$ 个物品进行分组的最小“不整齐程度”,起初 $f_0=0$,有如下转移:
$$f_i=\begin{cases}v_i-v_1 & 1\le i< 2k \\\min \limits_{j=0}^{i-k}(f_j+v_i-v_{j+1}) & 2k\le i\le n \end{cases}$$
对于 $2k\le i\le n$ 的情况,枚举每个 $j$,取最小的 $f_j-v_{j+1}$ 进行转移,$f_n$ 即为最终答案。
转移次数 $O(n)$,转移复杂度 $O(n)$,总的时间复杂度为 $O(n^2)$。
考虑对上述 $O(n^2)$ 的做法进行优化。
复杂度高的原因出在每次取 $\min \limits_{j=0}^{i-k}(f_j-v_{j+1})$ 时需要 $O(n)$ 枚举 $j$,而实际上 $\left [0,i-k \right ]$ 是一个左端静止且右边递增的区间,因此可以直接用一个变量维护这个最值(甚至不需要写单调队列)。
现在,转移次数 $O(n)$,转移复杂度 $O(1)$,DP 的时间复杂度为 $O(n)$,由于排序是 $O(n \log n)$ 的,故总的时间复杂度为 $O(n \log n)$,可以通过此题。
#include<iostream>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e6+5;
const ll inf=1e18;
int n,k;
ll v[N],f[N],mini;
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
{
scanf("%d",&v[i]);
}
sort(v+1,v+1+n);
for(int i=1;i<=n;i++)
{
if(i<k*2)f[i]=v[i]-v[1];
else
{
mini=min(mini,f[i-k]-v[i-k+1]);
f[i]=v[i]+mini;
}
}
printf("%lld",f[n]);
return 0;
}
$O(n^2)$ 求出任意两点之间的边权(即时间,下同),直接从 $1$ 跑最短路。
考虑边权与坐标的关系,若从一点 $i$ 走到 $j$ 的时间是 $|x_i-x_j|$,且存在另一点 $k$ 满足 $x_k$ 在 $x_i,x_j$ 之间,那么边 $(i,j)$ 可被边 $(i,k),(k,j)$ 代替,也即经过 $k$ 不会使时间更长,这是因为:
$$\begin{aligned} dis_{i,j} &= |x_i-x_j| \\ &= |x_i-x_k| + |x_k-x_j| \\ &\ge \min \left\{ |x_i-x_k|,|y_i-y_k| \right\} + \min \left\{ |x_k-x_j|,|y_k-y_j| \right\} \\ &= dis_{i,k} +dis_{k,j} \end{aligned}$$
纵坐标同理。
对此推广,我们得到结论:对所有点按横坐标排序,相邻点连边,然后纵坐标做相同处理,然后跑最短路即可。边数是 $O(n)$ 的,可以通过此题。
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e5+5;
const ll inf=1e18;
int n;
struct node
{
int num;
ll x,y;
}dot[N];
bool cmp1(node a,node b)
{
return a.x<b.x;
}
bool cmp2(node a,node b)
{
return a.y<b.y;
}
struct edge
{
int v;
ll w;
};
vector<edge>e[N];
priority_queue<pair<ll,int> >q;
ll d[N];
bool vis[N];
void dijkstra()
{
for(int i=1;i<=n;i++)d[i]=inf;
d[1]=0;
q.push(make_pair(0,1));
while(!q.empty())
{
int u0=q.top().second;
q.pop();
if(vis[u0])continue;
vis[u0]=1;
for(int i=0;i<e[u0].size();i++)
{
int v0=e[u0][i].v,w0=e[u0][i].w;
if(d[u0]+w0<d[v0])
{
d[v0]=d[u0]+w0;
q.push(make_pair(-d[v0],v0));
}
}
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
dot[i].num=i;
scanf("%lld%lld",&dot[i].x,&dot[i].y);
}
sort(dot+1,dot+1+n,cmp1);
for(int i=1;i<n;i++)
{
int u0=dot[i].num,v0=dot[i+1].num,
w0=dot[i+1].x-dot[i].x;
e[u0].push_back({v0,w0});
e[v0].push_back({u0,w0});
}
sort(dot+1,dot+1+n,cmp2);
for(int i=1;i<n;i++)
{
int u0=dot[i].num,v0=dot[i+1].num,
w0=dot[i+1].y-dot[i].y;
e[u0].push_back({v0,w0});
e[v0].push_back({u0,w0});
}
dijkstra();
printf("%lld",d[n]);
return 0;
}
$O(n^3)$ 超级大暴力,略。
容易知道均衡区间不存在,答案全是 $0$。
建 $ST$ 表维护区间 $min$ 和 $\max$,$O(1)$ 判断一个区间是否合法,时间复杂度 $O(n^2)$。
这一档区分出常数太大的 $\log$ 以及一些神秘复杂度,像根号、$\log^2$ 之类的,放到下面讲。
我们考虑位置 $i$ 成为一个均衡区间的左端点需要满足的限制(右端点同理):
也就是说,$i$ 符合条件需要右端点 $j$ 足够大。
我们做四次单调栈分别预处理出每个 $v_i$ 的左侧和右侧第一个大于或小于 $v_i$ 的数的位置(不存在则用 $0$ 或 $n+1$ 补充)。左侧的两个位置取 $\min$,记为 $L_i$;右侧取 $\max$,记为 $R_i$。
那么 $[i,j]$ 是均衡区间的充要条件是,$j>R_i$ 且 $L_j>i$。
因此,将所有 $(j,L_j)$ 视为平面上的点,$i$ 的答案即平面上符合上述条件的点的个数,离线下来用树状数组做二位数点即可。注意要离散化。
这个做法有 $O(n\log n)$ 的时间复杂度和 $O(n)$ 的空间复杂度。